Skip to content

Commit

Permalink
Merge pull request #475 from jmpsec/env-enroll-api
Browse files Browse the repository at this point in the history
Get enroll and remove values from API for an environment
  • Loading branch information
javuto authored Aug 12, 2024
2 parents 1b67aa4 + c48c4a1 commit 7663613
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 0 deletions.
140 changes: 140 additions & 0 deletions api/handlers-environments.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"net/http"

"github.com/gorilla/mux"
"github.com/jmpsec/osctrl/environments"
"github.com/jmpsec/osctrl/settings"
"github.com/jmpsec/osctrl/types"
"github.com/jmpsec/osctrl/users"
"github.com/jmpsec/osctrl/utils"
)
Expand Down Expand Up @@ -80,3 +82,141 @@ func apiEnvironmentsHandler(w http.ResponseWriter, r *http.Request) {
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, envAll)
incMetric(metricAPIEnvsOK)
}

// GET Handler to return node enrollment values (secret, certificate, one-liner) for an environment as JSON
func apiEnvEnrollHandler(w http.ResponseWriter, r *http.Request) {
incMetric(metricAPIEnvsReq)
utils.DebugHTTPDump(r, settingsmgr.DebugHTTP(settings.ServiceAPI, settings.NoEnvironmentID), false)
vars := mux.Vars(r)
// Extract environment
envVar, ok := vars["env"]
if !ok {
apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, nil)
incMetric(metricAPIEnvsErr)
return
}
// Get environment by name
env, err := envs.Get(envVar)
if err != nil {
if err.Error() == "record not found" {
apiErrorResponse(w, "environment not found", http.StatusNotFound, err)
} else {
apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, err)
}
incMetric(metricAPIEnvsErr)
return
}
// Get context data and check access
ctx := r.Context().Value(contextKey(contextAPI)).(contextValue)
if !apiUsers.CheckPermissions(ctx[ctxUser], users.UserLevel, env.UUID) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
incMetric(metricAPIEnvsErr)
return
}
// Extract target
targetVar, ok := vars["target"]
if !ok {
apiErrorResponse(w, "error getting target", http.StatusInternalServerError, nil)
incMetric(metricAPIEnvsErr)
return
}
var returnData string
switch targetVar {
case settings.DownloadSecret:
returnData = env.Secret
case settings.DownloadCert:
returnData = env.Certificate
case settings.DownloadFlags:
returnData = env.Flags
case environments.EnrollShell:
returnData, err = environments.QuickAddOneLinerShell((env.Certificate != ""), env)
if err != nil {
apiErrorResponse(w, "error generating sh one-liner", http.StatusInternalServerError, err)
incMetric(metricAPIEnvsErr)
return
}
case environments.EnrollPowershell:
returnData, err = environments.QuickAddOneLinerPowershell((env.Certificate != ""), env)
if err != nil {
apiErrorResponse(w, "error generating ps1 one-liner", http.StatusInternalServerError, err)
incMetric(metricAPIEnvsErr)
return
}
default:
apiErrorResponse(w, "invalid target", http.StatusBadRequest, fmt.Errorf("invalid target %s", targetVar))
incMetric(metricAPIEnvsErr)
return
}
// Serialize and serve JSON
if settingsmgr.DebugService(settings.ServiceAPI) {
log.Printf("DebugService: Returned environment %s", returnData)
}
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiDataResponse{Data: returnData})
incMetric(metricAPIEnvsOK)
}

// GET Handler to return node removal values for an environment as JSON
func apiEnvRemoveHandler(w http.ResponseWriter, r *http.Request) {
incMetric(metricAPIEnvsReq)
utils.DebugHTTPDump(r, settingsmgr.DebugHTTP(settings.ServiceAPI, settings.NoEnvironmentID), false)
vars := mux.Vars(r)
// Extract environment
envVar, ok := vars["env"]
if !ok {
apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, nil)
incMetric(metricAPIEnvsErr)
return
}
// Get environment by name
env, err := envs.Get(envVar)
if err != nil {
if err.Error() == "record not found" {
apiErrorResponse(w, "environment not found", http.StatusNotFound, err)
} else {
apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, err)
}
incMetric(metricAPIEnvsErr)
return
}
// Get context data and check access
ctx := r.Context().Value(contextKey(contextAPI)).(contextValue)
if !apiUsers.CheckPermissions(ctx[ctxUser], users.UserLevel, env.UUID) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
incMetric(metricAPIEnvsErr)
return
}
// Extract target
targetVar, ok := vars["target"]
if !ok {
apiErrorResponse(w, "error getting target", http.StatusInternalServerError, nil)
incMetric(metricAPIEnvsErr)
return
}
var returnData string
switch targetVar {
case environments.RemoveShell:
returnData, err = environments.QuickRemoveOneLinerShell((env.Certificate != ""), env)
if err != nil {
apiErrorResponse(w, "error generating sh one-liner", http.StatusInternalServerError, err)
incMetric(metricAPIEnvsErr)
return
}
case environments.RemovePowershell:
returnData, err = environments.QuickRemoveOneLinerPowershell((env.Certificate != ""), env)
if err != nil {
apiErrorResponse(w, "error generating ps1 one-liner", http.StatusInternalServerError, err)
incMetric(metricAPIEnvsErr)
return
}
default:
apiErrorResponse(w, "invalid target", http.StatusBadRequest, fmt.Errorf("invalid target %s", targetVar))
incMetric(metricAPIEnvsErr)
return
}
// Serialize and serve JSON
if settingsmgr.DebugService(settings.ServiceAPI) {
log.Printf("DebugService: Returned environment %s", types.ApiDataResponse{Data: returnData})
}
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, env)
incMetric(metricAPIEnvsOK)
}
4 changes: 4 additions & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,10 @@ func osctrlAPIService() {
// API: environments by environment
routerAPI.Handle(_apiPath(apiEnvironmentsPath)+"/{env}", handlerAuthCheck(http.HandlerFunc(apiEnvironmentHandler))).Methods("GET")
routerAPI.Handle(_apiPath(apiEnvironmentsPath)+"/{env}/", handlerAuthCheck(http.HandlerFunc(apiEnvironmentHandler))).Methods("GET")
routerAPI.Handle(_apiPath(apiEnvironmentsPath)+"/{env}/enroll/{target}", handlerAuthCheck(http.HandlerFunc(apiEnvEnrollHandler))).Methods("GET")
routerAPI.Handle(_apiPath(apiEnvironmentsPath)+"/{env}/enroll/{target}/", handlerAuthCheck(http.HandlerFunc(apiEnvEnrollHandler))).Methods("GET")
routerAPI.Handle(_apiPath(apiEnvironmentsPath)+"/{env}/remove/{target}", handlerAuthCheck(http.HandlerFunc(apiEnvironmentHandler))).Methods("GET")
routerAPI.Handle(_apiPath(apiEnvironmentsPath)+"/{env}/remove/{target}/", handlerAuthCheck(http.HandlerFunc(apiEnvironmentHandler))).Methods("GET")
routerAPI.Handle(_apiPath(apiEnvironmentsPath), handlerAuthCheck(http.HandlerFunc(apiEnvironmentsHandler))).Methods("GET")
routerAPI.Handle(_apiPath(apiEnvironmentsPath)+"/", handlerAuthCheck(http.HandlerFunc(apiEnvironmentsHandler))).Methods("GET")
// API: tags
Expand Down
112 changes: 112 additions & 0 deletions osctrl-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,118 @@ paths:
security:
- Authorization:
- read
/environments/{env}/enroll/{target}:
get:
tags:
- environments
summary: Get enroll values for an environment
description: Returns each of the node enrollment values (secret, certificate, flags, one-liner) for the requested osctrl environment
operationId: apiEnvEnrollHandler
parameters:
- name: env
in: path
description: Name or UUID of the requested osctrl environment
required: true
schema:
type: string
- name: target
in: path
description: Target to retrieve (secret, cert, flags, enroll.sh, enroll.ps1)
required: true
schema:
type: string
responses:
200:
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ApiDataResponse"
400:
description: bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
403:
description: no access
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
404:
description: no environments
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
500:
description: error getting environments
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
security:
- Authorization:
- read
/environments/{env}/remove/{target}:
get:
tags:
- environments
summary: Get remove values for an environment
description: Returns each of the node removal values (one-liner shell or powershell) for the requested osctrl environment
operationId: apiEnvironmentHandler
parameters:
- name: env
in: path
description: Name or UUID of the requested osctrl environment
required: true
schema:
type: string
- name: target
in: path
description: Target to retrieve (remove.sh, remove.ps1)
required: true
schema:
type: string
responses:
200:
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ApiDataResponse"
400:
description: bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
403:
description: no access
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
404:
description: no environments
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
500:
description: error getting environments
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
security:
- Authorization:
- read
/tags:
get:
tags:
Expand Down
5 changes: 5 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ type ApiGenericResponse struct {
Message string `json:"message"`
}

// ApiDataResponse to be returned to API requests for generic data
type ApiDataResponse struct {
Data string `json:"data"`
}

// ApiLoginResponse to be returned to API login requests with the generated token
type ApiLoginResponse struct {
Token string `json:"token"`
Expand Down

0 comments on commit 7663613

Please sign in to comment.