Compare commits

...

10 Commits

Author SHA1 Message Date
Tyler Perkins ad92a7ebba Add docker 2022-04-30 20:13:39 -04:00
Tyler Perkins 7e8f1e30b7 Add thread cli option 2022-04-30 14:41:17 -04:00
Tyler Perkins d2ad27ea3b Update features 2022-04-29 16:31:21 -04:00
Tyler Perkins ab9010d945 Add authorization 2022-04-29 16:26:29 -04:00
Tyler Perkins 0a9af99427 Add proposed documentation 2022-04-29 15:57:25 -04:00
Tyler Perkins 146d31efea Use sane constexpr var names 2022-04-29 15:54:37 -04:00
Tyler Perkins 3db4a8fcaa Update README and Makefile 2022-04-29 15:20:19 -04:00
Tyler Perkins 174e1ac2a9 Add up path 2022-04-29 14:02:33 -04:00
Tyler Perkins 654dcbc065 Add hostname paths 2022-04-29 13:54:52 -04:00
Tyler Perkins 5f85e772d8 Add catchall route 2022-04-29 13:39:20 -04:00
16 changed files with 304 additions and 20 deletions

View File

@ -19,6 +19,16 @@ types
* Returns exact response from the `/proc` file system
* For more about the format of these responses, refer to the proc(5) manpage
Security tokens
---------------
The application provides the `-a` flag, which accepts a path as a required
argument. The file specified in the path should contain a series of strings,
each on a new line. Each line represents a valid token that the client can send
to authorize the request. This token must be send in a `Authorization:` header.
If the token is not given, or the wrong token is given, the server will instead
return a 403.
Proc file responses
============
@ -65,7 +75,21 @@ Sample response
}
```
Special formatted responses
[GET] /proc/sys/kernel/hostname
--------------------------------
* returns hostname
* Same as /proc/sys/kernel/hostname file
Sample response
```
{
"hostname": "Samplebox"
}
```
Special formatted responses and aliases
===========================
[GET] /uptime
@ -94,3 +118,14 @@ Sample response
"memavailable" : 12324924,
}
```
[GET] /hostname
---------------
* Returns redirect to /proc/sys/kernel/hostname
[GET] /up
---------
* Always returns a 200 OK
* Can be used for testing latency

23
Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM ubuntu:latest AS build
RUN apt-get update && apt-get install -y curl build-essential git libboost-all-dev
RUN curl https://github.com/CrowCpp/Crow/releases/download/v1.0%2B2/crow-v1.0+2.deb -o crow.deb -L
RUN apt-get purge -y curl
RUN apt install -y ./crow.deb
WORKDIR /work
COPY ./ ./
RUN make clean && make
FROM ubuntu:latest
#WORKDIR /opt/proc-api
COPY --from=build /work/bin/proc-api ./
ENV PORT="5000"
ENV NAME="proc-api"
ENV THREADS="2"
ENV AUTH=""
CMD ./proc-api -p $PORT -n $NAME -t $THREADS $(if [ -z $AUTH ]; then echo ""; else echo "-a $AUTH"; fi)

View File

@ -10,7 +10,7 @@ FLAGS = -pipe
CFLAGS = -Wall
CFLAGS += -Ofast
#CFLAGS += -flto
CFLAGS += -flto
#CFLAGS += -g
#CFLAGS += -pg
@ -23,7 +23,7 @@ BIN = ./bin
PREFIX = /usr/local
MANPREFIX = $(PREFIX)/share/man
TARGET = crowtest
TARGET = proc-api
MAKEFLAGS += --jobs=4

View File

@ -1,4 +1,25 @@
Learn crow
Proc-API
==========
Learning the crow web framework
This is a lightweight, fast, RESTful API designed around the /proc filesystem
in linux. It allows for stateless monitoring of remote linux systems with
automated tools.
What is this good for?
----------------------
* Remote monitoring
* Collecting stats on a remote system
* Client applications for VPS hosters
How do I talk to the API?
-------------------------
See [DOCUMENTATION.md](DOCUMENTATION.md)
Features/TODO
-------------
* [X] Basic routing
* [X] Authentication
* [ ] Support for querying a PID

20
docker-compose.yml Normal file
View File

@ -0,0 +1,20 @@
# NOTE: This is a sample docker-compose for how you could set up your own set
# of containers
version: '3'
services:
proc-api_service:
image: proc-api
container_name: proc-api_service
restart: unless-stopped
ports:
- 5000:5000
- 6123:6123
environment:
- PORT=6123
- NAME="My Cool Service"
- THREADS=4
#-AUTH="./auth.txt"
#NOTE for auth to work, you will need to mount a volume with the authfile
#in it

37
src/auth/auth.cpp Normal file
View File

@ -0,0 +1,37 @@
///////////////////////////////////////////////////////////////////////////////
// Tyler Perkins
// 29-4-22
// Auth definition
//
#include "auth.hpp"
bool auth::checkAuth(const crow::request& req){
if(auth::auth_check::checker == nullptr)
return true;
else
return auth::auth_check::checker->checkAuth(req);
}
auth::auth_check::auth_check(std::string& path){
std::ifstream f (path);
if(f.is_open()){
std::string line;
while(std::getline(f, line)){
tokens.insert(line);
}
}
f.close();
}
auth::auth_check::~auth_check(){
//nothing to clean up
}
bool auth::auth_check::checkAuth(const crow::request& req) const {
if(tokens.find(req.get_header_value(AUTH_HEADER)) != tokens.end())
return true;
else
return false;
}

31
src/auth/auth.hpp Normal file
View File

@ -0,0 +1,31 @@
///////////////////////////////////////////////////////////////////////////////
// Tyler Perkins
// 29-4-22
// Auth definition
//
#pragma once
#include <crow.h>
#include <unordered_set>
#include <string>
#include <fstream>
constexpr char AUTH_HEADER[] = "Authorization";
namespace auth{
class auth_check {
public:
auth_check(std::string&);
~auth_check();
static auth_check* checker;
bool checkAuth(const crow::request&) const;
private:
std::unordered_set<std::string> tokens;
};
bool checkAuth(const crow::request&);
}

View File

@ -7,7 +7,7 @@
#include "memory.hpp"
bool memory::getProcMem(crow::json::wvalue& ret){
std::ifstream f (procmempath);
std::ifstream f (PROC_MEM_PATH);
std::string line;
if(f.is_open()){
while(std::getline(f, line)){
@ -36,7 +36,7 @@ bool memory::getProcMem(crow::json::wvalue& ret){
}
bool memory::getRawProcMem(std::string& ret){
std::ifstream f (procmempath);
std::ifstream f (PROC_MEM_PATH);
std::string line;
if(f.is_open()){
while(std::getline(f, line)){
@ -53,7 +53,7 @@ bool memory::getRawProcMem(std::string& ret){
}
bool memory::getEasyMem(crow::json::wvalue& ret){
std::ifstream f (procmempath);
std::ifstream f (PROC_MEM_PATH);
std::string line;
if(f.is_open()){
for(int j = 0; j < 3; ++j){

View File

@ -11,7 +11,7 @@
#include <string>
#include <fstream>
constexpr char procmempath[] = "/proc/meminfo";
constexpr char PROC_MEM_PATH[] = "/proc/meminfo";
namespace memory{
bool getProcMem(crow::json::wvalue&);

View File

@ -7,7 +7,7 @@
#include "state.hpp"
bool state::getUptime(crow::json::wvalue& ret){
std::ifstream f (procuptimepath);
std::ifstream f (PROC_UPTIME_PATH);
std::string line;
if(f.is_open()){
int space = -1;
@ -34,7 +34,7 @@ bool state::getUptime(crow::json::wvalue& ret){
}
bool state::getRawUptime(std::string& ret){
std::ifstream f (procuptimepath);
std::ifstream f (PROC_UPTIME_PATH);
if(f.is_open()){
std::getline(f, ret);
ret += '\n';
@ -48,7 +48,7 @@ bool state::getRawUptime(std::string& ret){
}
bool state::getLoadAvg(crow::json::wvalue& ret){
std::ifstream f (procloadavgpath);
std::ifstream f (PROC_LOADAVG_PATH);
std::string line;
if(f.is_open()){
int spaces[4];
@ -78,7 +78,37 @@ bool state::getLoadAvg(crow::json::wvalue& ret){
}
bool state::getRawLoadAvg(std::string& ret){
std::ifstream f (procloadavgpath);
std::ifstream f (PROC_LOADAVG_PATH);
if(f.is_open()){
std::getline(f, ret);
ret += '\n';
f.close();
} else {
ret = "Failed to open proc filesystem";
return false;
}
return true;
}
bool state::getHostname(crow::json::wvalue& ret){
std::ifstream f (PROC_HOSTNAME_PATH);
std::string line;
if(f.is_open()){
std::getline(f, line);
ret["hostname"] = line;
f.close();
} else {
ret["message"] = "Failed to open proc filesystem";
return false;
}
return true;
}
bool state::getRawHostname(std::string& ret){
std::ifstream f (PROC_HOSTNAME_PATH);
if(f.is_open()){
std::getline(f, ret);
ret += '\n';

View File

@ -11,12 +11,15 @@
#include <string>
#include <fstream>
constexpr char procuptimepath[] = "/proc/uptime";
constexpr char procloadavgpath[] = "/proc/loadavg";
constexpr char PROC_UPTIME_PATH[] = "/proc/uptime";
constexpr char PROC_LOADAVG_PATH[] = "/proc/loadavg";
constexpr char PROC_HOSTNAME_PATH[] = "/proc/sys/kernel/hostname";
namespace state{
bool getUptime(crow::json::wvalue&);
bool getRawUptime(std::string&);
bool getLoadAvg(crow::json::wvalue&);
bool getRawLoadAvg(std::string&);
bool getHostname(crow::json::wvalue&);
bool getRawHostname(std::string&);
}

View File

@ -7,6 +7,9 @@
#include <iostream>
#include <crow.h>
#include "opt/parseopt.hpp"
#include "auth/auth.hpp"
auth::auth_check* auth::auth_check::checker;
#include "routes.hpp"
@ -14,13 +17,18 @@ int main(int argc, char** argv){
option_flags* flags = parse_options(argc, argv);
if(!flags->auth_path.empty()){
auth::auth_check::checker = new auth::auth_check(flags->auth_path);
CROW_LOG_INFO << "Added authorization using authfile " << flags->auth_path;
}
crow::SimpleApp app;
setRoutes(app);
app.port(flags->port)
.server_name(flags->name)
.multithreaded();
.concurrency(flags->threads);
delete flags;

View File

@ -11,6 +11,9 @@ void help(char* progName){
std::cout << "Options:\n";
std::cout << " [-p PORT] Port to listen on (Default 5000)\n";
std::cout << " [-n NAME] Server name (Default \"proc-api\")\n";
std::cout << " [-a PATH] Path to file containing authorization tokens\n";
std::cout << " If the -a flag is not passed, no authorization is used\n";
std::cout << " [-t THREADS] Number of threads to use. By default, uses 2 threads\n";
std::cout << " [-h] Display this help message\n\n";
exit(1);
}
@ -21,7 +24,9 @@ option_flags* parse_options(int argc, char** argv){
option_flags* ret = new option_flags;
ret->port = 5000;
ret->threads = 2;
ret->name = "proc-api";
ret->auth_path = "";
while((c = getopt(argc, argv, optarg_string)) != -1){
switch(c){
@ -31,6 +36,14 @@ option_flags* parse_options(int argc, char** argv){
case 'n':
ret->name = std::string(optarg);
break;
case 'a':
ret->auth_path = std::string(optarg);
break;
case 't':
ret->threads = atoi(optarg);
if(ret->threads == 0)
ret->threads = 1;
break;
case '?':
std::cerr << "Unkown option: " << (char)optopt << "\n";
case 'h':

View File

@ -12,11 +12,13 @@
///////////////////////////////////////
// cli options
constexpr char optarg_string[] = "n:p:h";
constexpr char optarg_string[] = "n:p:a:t:h";
struct option_flags {
uint16_t port;
uint16_t threads;
std::string name;
std::string auth_path;
};
void help(char*);

View File

@ -8,6 +8,9 @@
void setRoutes(crow::SimpleApp& app){
CROW_ROUTE(app, "/proc/meminfo")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
bool status;
std::string accept = req.get_header_value("Accept");
@ -24,7 +27,9 @@ void setRoutes(crow::SimpleApp& app){
}
});
CROW_ROUTE(app, "/mem")([](){
CROW_ROUTE(app, "/mem")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
bool status;
crow::json::wvalue json;
@ -34,6 +39,8 @@ void setRoutes(crow::SimpleApp& app){
});
CROW_ROUTE(app, "/proc/uptime")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
bool status;
std::string accept = req.get_header_value("Accept");
@ -50,13 +57,17 @@ void setRoutes(crow::SimpleApp& app){
}
});
CROW_ROUTE(app, "/uptime")([](){
CROW_ROUTE(app, "/uptime")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
crow::response ret;
ret.moved_perm("/proc/uptime");
return ret;
});
CROW_ROUTE(app, "/proc/loadavg")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
bool status;
std::string accept = req.get_header_value("Accept");
@ -73,10 +84,58 @@ void setRoutes(crow::SimpleApp& app){
}
});
CROW_ROUTE(app, "/load")([](){
CROW_ROUTE(app, "/load")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
crow::response ret;
ret.moved_perm("/proc/loadavg");
return ret;
});
CROW_ROUTE(app, "/proc/sys/kernel/hostname")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
bool status;
std::string accept = req.get_header_value("Accept");
std::transform(accept.begin(), accept.end(), accept.begin(), ::tolower);
if(accept == "text/plain"){
accept.clear();
status = state::getRawHostname(accept);
return crow::response(status ? 200 : 503, accept);
} else {
crow::json::wvalue json;
status = state::getHostname(json);
return crow::response(status ? 200 : 503, json.dump());
}
});
CROW_ROUTE(app, "/hostname")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
crow::response ret;
ret.moved_perm("/proc/sys/kernel/hostname");
return ret;
});
CROW_ROUTE(app, "/up")([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
crow::json::wvalue ret;
ret["message"] = "Alive and well!";
return crow::response(200, ret.dump());
});
//catchall route
CROW_CATCHALL_ROUTE(app)([](const crow::request& req){
if(!auth::checkAuth(req))
return crow::response(403, "Authentication required");
crow::json::wvalue ret;
ret["message"] = "Route not understood. Please refer to the documentation";
return crow::response(404, ret.dump());
});
}

View File

@ -13,4 +13,6 @@
#include "components/memory.hpp"
#include "components/state.hpp"
#include "auth/auth.hpp"
void setRoutes(crow::SimpleApp&);