diff --git a/CHANGELOG.md b/CHANGELOG.md index 29f6e6aaa..3fa021fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the ZSS package will be documented in this file. ## `1.25.0` +- Enhancement: Added RBAC capability via SAF checks - Enhancement: Add an endpoint for PassTicket generation - Enhancement: Add an endpoint for user info - Enhancement: Added method to read and set loglevel of dataservices @@ -23,7 +24,6 @@ All notable changes to the ZSS package will be documented in this file. - Bugfix: Fix `zis-plugin-install.sh` script to properly exit on error with extended-install - Bugfix: When builtin TLS was enabled, a small leak occurred when closing sockets. - ## `1.23.0` - Bugfix: `relativeTo` parsing may have failed depending upon path length and contents, leading to skipped plugin loading. @@ -31,8 +31,6 @@ All notable changes to the ZSS package will be documented in this file. ## `1.22.0` -### New features and enhancements - - Bugfix: Dataset contents API doesn't skip empty records while reading a dataset - Enhancement: Plugins can push state out to the Caching Service for high availability storage via a storage API, available to dataservices as `remoteStorage` - Enhancement: Plugins can push state out to the In-Memory Storage via a storage API, available to dataservices as `localStorage` diff --git a/c/authService.c b/c/authService.c index 732df59a5..a5371905f 100644 --- a/c/authService.c +++ b/c/authService.c @@ -35,8 +35,9 @@ #include "httpserver.h" #include "zssLogging.h" -#define SAF_CLASS "ZOWE" -#define JSON_ERROR_BUFFER_SIZE 1024 +#define JSON_ERROR_BUFFER_SIZE 1024 +#define STRING_BUFFER_SIZE 1024 +#define SAF_SUB_URL_SIZE 32 #define SAF_PASSWORD_RESET_RC_OK 0 #define SAF_PASSWORD_RESET_RC_WRONG_PASSWORD 111 @@ -45,7 +46,10 @@ #define SAF_PASSWORD_RESET_RC_NO_NEW_PASSSWORD 168 #define SAF_PASSWORD_RESET_RC_WRONG_FORMAT 169 -#define RESPONSE_MESSAGE_LENGTH 100 +#define RESPONSE_MESSAGE_LENGTH 100 + +#define SAF_PLUGIN_ID "ORG.ZOWE.CONFIGJS" +#define SAF_SERVICE_NAME "DATA" /* * A handler performing the SAF_AUTH check: checks if the user has the @@ -66,6 +70,26 @@ static int serveAuthCheck(HttpService *service, HttpResponse *response); +static int makeProfileName( + char *profileName, + int profileNameBufSize, + const char *type, + const char *productCode, + int instanceID, + const char *pluginID, + const char *rootServiceName, + const char *serviceName, + const char *method, + const char *scope, + char subUrl[SAF_SUB_URL_SIZE][STRING_BUFFER_SIZE]); + +static void setProfileNameAttribs( + char *pluginID, + char *serviceName, + char *type, + char *scope, + char subUrl[SAF_SUB_URL_SIZE][STRING_BUFFER_SIZE]); + int installAuthCheckService(HttpServer *server) { // zowelog(NULL, 0, ZOWE_LOG_DEBUG2, "begin %s\n", // __FUNCTION__); @@ -74,6 +98,7 @@ int installAuthCheckService(HttpServer *server) { httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; httpService->serviceFunction = &serveAuthCheck; httpService->runInSubtask = FALSE; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; registerHttpService(server, httpService); // zowelog(NULL, 0, ZOWE_LOG_DEBUG2, "end %s\n", // __FUNCTION__); @@ -162,7 +187,7 @@ static int serveAuthCheck(HttpService *service, HttpResponse *res) { int rc = 0, rsn = 0, safStatus = 0; ZISAuthServiceStatus reqStatus = {0}; CrossMemoryServerName *privilegedServerName; - const char *userName = req->username, *class = SAF_CLASS; + const char *userName = req->username, *class = ZOWE_SAF_CLASS; rc = extractQuery(req->parsedFile, &entity, &accessStr); if (rc != 0) { respondWithError(res, HTTP_STATUS_BAD_REQUEST, "Broken auth query"); @@ -173,16 +198,218 @@ static int serveAuthCheck(HttpService *service, HttpResponse *res) { respondWithError(res, HTTP_STATUS_BAD_REQUEST, "Unexpected access level"); return 0; } - /* printf("query: user %s, class %s, entity %s, access %d\n", userName, class, - entity, access); */ + privilegedServerName = getConfiguredProperty(service->server, HTTP_SERVER_PRIVILEGED_SERVER_PROPERTY); rc = zisCheckEntity(privilegedServerName, userName, class, entity, access, &reqStatus); + respond(res, rc, &reqStatus); return 0; } +int verifyAccessToSafProfile(HttpServer *server, const char *userName, const char *entity, const int access) { + CrossMemoryServerName *privilegedServerName = getConfiguredProperty(server, HTTP_SERVER_PRIVILEGED_SERVER_PROPERTY); + ZISAuthServiceStatus reqStatus = {0}; + const char *class = ZOWE_SAF_CLASS; + + int rc = zisCheckEntity(privilegedServerName, userName, class, entity, access, &reqStatus); + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_DEBUG2, + "verifyAccessToSafProfile entity '%s' class '%s' access '%d' , rc: %d\n", entity, class, access, rc); + + return (rc != RC_ZIS_SRVC_OK) ? -1 : 0; +} + +int getProfileNameFromRequest(char *profileName, const int profileNameBufSize, StringList *parsedFile, const char *method, int instanceID) { + char type[STRING_BUFFER_SIZE] = {0}; // core || config || service + char productCode[STRING_BUFFER_SIZE] = {0}; + char rootServiceName[STRING_BUFFER_SIZE] = {0}; + char subUrl[SAF_SUB_URL_SIZE][STRING_BUFFER_SIZE] = {0}; + char scope[STRING_BUFFER_SIZE] = {0}; + char pluginID[STRING_BUFFER_SIZE] = {0}; + char serviceName[STRING_BUFFER_SIZE] = {0}; + char urlSegment[STRING_BUFFER_SIZE] = {0}; + int subUrlIndex = 0; + bool isRootServiceNameInited = false; + + snprintf(urlSegment, sizeof(urlSegment), "%s", stringListPrint(parsedFile, 1, 1, "/", 0)); + StringListElt *pathSegment = firstStringListElt(parsedFile); + + strupcase(urlSegment); + if (instanceID < 0) { // Set instanceID + instanceID = 0; + } + if (strcmp(urlSegment, "PLUGINS") != 0) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_DEBUG2, + "parsedFile urlSegment check didn't match.\n"); + subUrlIndex = -1; + while (pathSegment != NULL) { + snprintf(urlSegment, sizeof(urlSegment), "%s", pathSegment->string); + strupcase(urlSegment); + if (!isRootServiceNameInited) { + snprintf(rootServiceName, sizeof(rootServiceName), urlSegment); + isRootServiceNameInited = true; + } else { //If URL subsections > SAF_SUB_URL_SIZE, we trim them from profile name (by not appending them) + if (subUrlIndex < SAF_SUB_URL_SIZE) { + snprintf(subUrl[subUrlIndex], sizeof(subUrl), urlSegment); + } + } + subUrlIndex++; + pathSegment = pathSegment->next; + } + snprintf(productCode, sizeof(productCode), "ZLUX"); + snprintf(type, sizeof(type), "core"); + } else { + subUrlIndex = 0; + + while (pathSegment != NULL) { + snprintf(urlSegment, sizeof(urlSegment), "%s", pathSegment->string); + strupcase(urlSegment); + switch(subUrlIndex) { + case 0: + snprintf(productCode, sizeof(productCode), urlSegment); + break; + case 1: + break; + case 2: + snprintf(pluginID, sizeof(pluginID), urlSegment); + break; + case 3: + break; + case 4: + snprintf(serviceName, sizeof(serviceName), urlSegment); + break; + case 5: + break; + default: { + int adjustedSubUrlIndex = subUrlIndex - 6; // subtract 6 from maximum index to begin init subUrl array at 0 + if (adjustedSubUrlIndex < SAF_SUB_URL_SIZE) { + snprintf(subUrl[adjustedSubUrlIndex], sizeof(subUrl), urlSegment); + } + } + } + subUrlIndex++; + pathSegment = pathSegment->next; + } + + setProfileNameAttribs(pluginID, serviceName, type, scope, subUrl); + int pluginIDLen = strlen(pluginID); + for (int index = 0; index < pluginIDLen; index++) { + if (pluginID[index] == '.') { + pluginID[index] = '_'; + } + } + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_DEBUG2, + "parsedFile urlSegment check OK.\n"); + } + return makeProfileName(profileName, profileNameBufSize, + type, + productCode, + instanceID, + pluginID, + rootServiceName, + serviceName, + method, + scope, + subUrl); +} + +static void setProfileNameAttribs( + char *pluginID, + const char *serviceName, + char *type, + char *scope, + char subUrl[SAF_SUB_URL_SIZE][STRING_BUFFER_SIZE]) { + if ((strcmp(pluginID, SAF_PLUGIN_ID) == 0) && (strcmp(serviceName, SAF_SERVICE_NAME) == 0)) + { + snprintf(type, STRING_BUFFER_SIZE, "config"); + snprintf(pluginID, STRING_BUFFER_SIZE, subUrl[0]); + snprintf(scope, STRING_BUFFER_SIZE, subUrl[1]); + + } else { + snprintf(type, STRING_BUFFER_SIZE, "service"); + } +} + +static int makeProfileName( + char *profileName, + const int profileNameBufSize, + const char *type, + const char *productCode, + int instanceID, + const char *pluginID, + const char *rootServiceName, + const char *serviceName, + const char *method, + const char *scope, + char subUrl[SAF_SUB_URL_SIZE][STRING_BUFFER_SIZE]) { + if (instanceID == -1) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Broken SAF query. Missing instance ID.\n"); + return -1; + } + if (method == NULL) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Broken SAF query. Missing method.\n"); + return -1; + } + int pos = 0; + if (strcmp(type, "service") == 0) { + if (pluginID == NULL) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Broken SAF query. Missing plugin ID.\n"); + return -1; + } + if (serviceName == NULL) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Broken SAF query. Missing service name.\n"); + return -1; + } + pos = snprintf(profileName, profileNameBufSize, "%s.%d.SVC.%s.%s.%s", productCode, instanceID, pluginID, serviceName, method); + } else if (strcmp(type, "config") == 0) { + if (pluginID == NULL) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Broken SAF query. Missing plugin ID.\n"); + return -1; + } + if (scope == NULL) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Broken SAF query. Missing scope.\n"); + return -1; + } + pos = snprintf(profileName, profileNameBufSize, "%s.%d.CFG.%s.%s.%s", productCode, instanceID, pluginID, method, scope); + } else if (strcmp(type, "core") == 0) { + if (rootServiceName == NULL) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Broken SAF query. Missing root service name.\n"); + return -1; + } + pos = snprintf(profileName, profileNameBufSize, "%s.%d.COR.%s.%s", productCode, instanceID, method, rootServiceName); + } else if (pos < 0) { + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, + "Internal string encoding error.\n"); + return -1; + } + // Child endpoints housed via subUrl + int index = 0; + while (index < SAF_SUB_URL_SIZE && strcmp(subUrl[index], "") != 0) { + if (pos >= profileNameBufSize) { + break; + } + pos += snprintf(profileName + pos, profileNameBufSize - pos, ".%s", subUrl[index]); + index++; + } + if (pos >= profileNameBufSize) { + char errMsg[256]; + snprintf(errMsg, sizeof(errMsg), "Generated SAF query longer than %d\n", profileNameBufSize - 1); + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_WARNING, errMsg); + return -1; + } + zowelog(NULL, LOG_COMP_ID_SECURITY, ZOWE_LOG_DEBUG2, + "Finished generating profileName: %s\n", profileName); + return 0; +} + void respondWithJsonStatus(HttpResponse *response, const char *status, int statusCode, const char *statusMessage) { jsonPrinter *out = respondWithJsonPrinter(response); setResponseStatus(response,statusCode,(char *)statusMessage); diff --git a/c/datasetService.c b/c/datasetService.c index 3aabb11c9..c52cf839f 100644 --- a/c/datasetService.c +++ b/c/datasetService.c @@ -188,6 +188,7 @@ void installDatasetContentsService(HttpServer *server) { HttpService *httpService = makeGeneratedService("datasetContents", "/datasetContents/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; httpService->serviceFunction = serveDatasetContents; @@ -201,6 +202,7 @@ void installVSAMDatasetContentsService(HttpServer *server) { HttpService *httpService = makeGeneratedService("VSAMdatasetContents", "/VSAMdatasetContents/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; httpService->serviceFunction = serveVSAMDatasetContents; @@ -219,6 +221,7 @@ void installDatasetMetadataService(HttpServer *server) { HttpService *httpService = makeGeneratedService("datasetMetadata", "/datasetMetadata/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; httpService->serviceFunction = serveDatasetMetadata; diff --git a/c/omvsService.c b/c/omvsService.c index b0d22f5f2..146b0c801 100644 --- a/c/omvsService.c +++ b/c/omvsService.c @@ -54,6 +54,7 @@ int installOMVSService(HttpServer *server) { HttpService *httpService = makeGeneratedService("OMVS_Service", "/omvs/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = &serveOMVSSegment; httpService->runInSubtask = FALSE; httpService->doImpersonation = FALSE; diff --git a/c/securityService.c b/c/securityService.c index 8e7bb287e..8cf0d0906 100644 --- a/c/securityService.c +++ b/c/securityService.c @@ -3356,6 +3356,7 @@ void installSecurityManagementServices(HttpServer *server) { makeStringParamSpec("dryRun", SERVICE_ARG_OPTIONAL, NULL ))); classMgmtService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + classMgmtService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; classMgmtService->serviceFunction = &serveClassManagement; classMgmtService->runInSubtask = TRUE; classMgmtService->doImpersonation = TRUE; @@ -3370,6 +3371,7 @@ void installSecurityManagementServices(HttpServer *server) { makeStringParamSpec("dryRun", SERVICE_ARG_OPTIONAL, NULL ))); userProfileService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + userProfileService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; userProfileService->serviceFunction = &serveUserProfile; userProfileService->runInSubtask = TRUE; userProfileService->doImpersonation = TRUE; @@ -3384,6 +3386,7 @@ void installSecurityManagementServices(HttpServer *server) { makeStringParamSpec("dryRun", SERVICE_ARG_OPTIONAL, NULL ))); groupMgmtService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + groupMgmtService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; groupMgmtService->serviceFunction = &serveGroupManagement; groupMgmtService->runInSubtask = TRUE; groupMgmtService->doImpersonation = TRUE; @@ -3397,6 +3400,7 @@ void installSecurityManagementServices(HttpServer *server) { makeIntParamSpec("traceLevel", SERVICE_ARG_OPTIONAL, 0, 0, 0, 0, NULL )); accessService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + accessService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; accessService->serviceFunction = &serveAccessInfo; accessService->runInSubtask = TRUE; accessService->doImpersonation = TRUE; diff --git a/c/serverStatusService.c b/c/serverStatusService.c index 10633237d..45985abab 100644 --- a/c/serverStatusService.c +++ b/c/serverStatusService.c @@ -39,6 +39,7 @@ #include "httpserver.h" #include "logging.h" #include "zssLogging.h" +#include "serviceUtils.h" #include "serverStatusService.h" #ifdef __ZOWE_OS_ZOS @@ -49,7 +50,7 @@ static inline bool strne(const char *a, const char *b) { return a != NULL && b != NULL && strcmp(a, b) != 0; } -void installServerStatusService(HttpServer *server, JsonObject *serverSettings, char* productVer) { +void installServerStatusService(HttpServer *server, JsonObject *serverSettings, bool rbacEnabled, char* productVer) { HttpService *httpService = makeGeneratedService("Server_Status_Service", "/server/agent/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; httpService->authFlags |= SERVICE_AUTH_FLAG_OPTIONAL; @@ -61,6 +62,7 @@ void installServerStatusService(HttpServer *server, JsonObject *serverSettings, context->serverConfig = serverSettings; context->productVersion[sizeof(context->productVersion) - 1] = '\0'; strncpy(context->productVersion, productVer, sizeof(context->productVersion) - 1); + context->rbacEnabled = rbacEnabled; } httpService->userPointer = context; registerHttpService(server, httpService); @@ -243,10 +245,9 @@ static int serveStatus(HttpService *service, HttpResponse *response) { ServerAgentContext *context = service->userPointer; //This service is conditional on RBAC being enabled because it is a //sensitive URL that only RBAC authorized users should be able to get full access - JsonObject *dataserviceAuth = jsonObjectGetObject(context->serverConfig, "dataserviceAuthentication"); - int rbacParm = jsonObjectGetBoolean(dataserviceAuth, "rbac"); + bool rbacEnabled = context->rbacEnabled; int isAuthenticated = response->request->authenticated; - bool allowFullAccess = isAuthenticated && rbacParm; + bool allowFullAccess = isAuthenticated && rbacEnabled; if (!strcmp(request->method, methodGET)) { char *l1 = stringListPrint(request->parsedFile, 2, 1, "/", 0); if (!allowFullAccess && statusEndPointRequireAuthAndRBAC(l1)) { @@ -254,7 +255,7 @@ static int serveStatus(HttpService *service, HttpResponse *response) { respondWithError(response, HTTP_STATUS_UNAUTHORIZED, "Not Authorized"); return -1; } - if (!rbacParm) { + if (!rbacEnabled) { respondWithError(response, HTTP_STATUS_BAD_REQUEST, "Set dataserviceAuthentication.rbac to true in server configuration"); return -1; } diff --git a/c/unixFileService.c b/c/unixFileService.c index e06e777c5..938946406 100644 --- a/c/unixFileService.c +++ b/c/unixFileService.c @@ -1100,6 +1100,7 @@ void installUnixFileContentsService(HttpServer *server) { HttpService *httpService = makeGeneratedService("UnixFileContents", "/unixfile/contents/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveUnixFileContents; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; @@ -1119,6 +1120,7 @@ void installUnixFileRenameService(HttpServer *server) { HttpService *httpService = makeGeneratedService("UnixFileRename", "/unixfile/rename/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveUnixFileRename; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; @@ -1129,6 +1131,7 @@ void installUnixFileCopyService(HttpServer *server) { HttpService *httpService = makeGeneratedService("UnixFileCopy", "/unixfile/copy/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveUnixFileCopy; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; @@ -1139,6 +1142,7 @@ void installUnixFileMakeDirectoryService(HttpServer *server) { HttpService *httpService = makeGeneratedService("UnixFileMkdir", "/unixfile/mkdir/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveUnixFileMakeDirectory; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; @@ -1149,6 +1153,7 @@ void installUnixFileTouchService(HttpServer *server) { HttpService *httpService = makeGeneratedService("UnixFileTouch", "/unixfile/touch/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveUnixFileTouch; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; @@ -1159,6 +1164,7 @@ void installUnixFileMetadataService(HttpServer *server) { HttpService *httpService = makeGeneratedService("unixFileMetadata", "/unixfile/metadata/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; httpService->serviceFunction = serveUnixFileMetadata; @@ -1169,6 +1175,7 @@ void installUnixFileChangeOwnerService(HttpServer *server) { HttpService *httpService = makeGeneratedService("unixFileMetadata", "/unixfile/chown/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; httpService->serviceFunction = serveUnixFileChangeOwner; @@ -1179,6 +1186,7 @@ void installUnixFileChangeTagService(HttpServer *server) { HttpService *httpService = makeGeneratedService("UnixFileChtag", "/unixfile/chtag/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveUnixFileChangeTag; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; @@ -1189,6 +1197,7 @@ void installUnixFileChangeModeService(HttpServer *server) { HttpService *httpService = makeGeneratedService("UnixFileChmod", "/unixfile/chmod/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveUnixFileChangeMode; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; @@ -1199,6 +1208,7 @@ void installUnixFileTableOfContentsService(HttpServer *server) { HttpService *httpService = makeGeneratedService("unixFileMetadata", "/unixfile/"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->runInSubtask = TRUE; httpService->doImpersonation = TRUE; httpService->serviceFunction = serveTableOfContents; diff --git a/c/zss.c b/c/zss.c index ebb2f8864..82eab03a5 100644 --- a/c/zss.c +++ b/c/zss.c @@ -100,9 +100,11 @@ char productVersion[40]; static JsonObject *MVD_SETTINGS = NULL; +static JsonObject *ENV_SETTINGS = NULL; static int traceLevel = 0; #define JSON_ERROR_BUFFER_SIZE 1024 +#define STRING_BUFFER_SIZE 1024 #define DEFAULT_TLS_CIPHERS \ TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 \ @@ -320,10 +322,85 @@ static void setPrivilegedServerName(HttpServer *server, JsonObject *mvdSettings, } #endif /* __ZOWE_OS_ZOS */ +typedef struct RbacAuthorizationData_t { + int instanceId; +} RbacAuthorizationData; + +static int rbacAuthorization(HttpService *service, HttpRequest *request, HttpResponse *response, void *userData) { + if (request->username == NULL) { + // username is required to perform RBAC check + // would a check for (service->authType != SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN) be better? + return TRUE; + } + + RbacAuthorizationData *rbacData = userData; + + char method[16]; + if (snprintf(method, sizeof(method), "%s", request->method) >= sizeof(method)) { + zowelog(NULL, LOG_COMP_ID_MVD_SERVER, ZOWE_LOG_DEBUG, "HTTP method too long\n"); + return FALSE; + }; + destructivelyNativize(method); + + char profileName[ZOWE_PROFILE_NAME_LEN+1] = {0}; + int rc = getProfileNameFromRequest(profileName, sizeof(profileName), request->parsedFile, method, rbacData->instanceId); + if (rc != 0) { + return FALSE; + } + + rc = verifyAccessToSafProfile(service->server, request->username, profileName, SAF_AUTH_ATTR_READ); + if (rc != 0) { + return FALSE; + } + + return TRUE; +} + +static bool isRbacEnabled(JsonObject *mvdSettings, JsonObject *envSettings) { + int rbacParm = FALSE; + Json *rbacObj = jsonObjectGetPropertyValue(envSettings, "ZWED_dataserviceAuthentication_rbac"); + if (rbacObj == NULL) { + JsonObject *dataserviceAuth = jsonObjectGetObject(mvdSettings, "dataserviceAuthentication"); + if (dataserviceAuth) { + rbacParm = jsonObjectGetBoolean(dataserviceAuth, "rbac"); + } + } else { + rbacParm = jsonAsBoolean(rbacObj); + } + return rbacParm; +} + +static int getZoweInstanceId(void) { + char *instance = getenv("ZOWE_INSTANCE"); + if (!instance) { + return 0; + } + return atoi(instance); +} + +/* Registers Authorization handlers, returns true on success, false on failure */ +static bool registerAuthorizationHandlers(HttpServer *server, bool rbacEnabled) { + if (!rbacEnabled) { + return true; + } + RbacAuthorizationData *rbacData = (RbacAuthorizationData*) safeMalloc(sizeof(*rbacData), "Rbac Authorization Data"); + if (!rbacData) { + zowelog(NULL, LOG_COMP_ID_MVD_SERVER, ZOWE_LOG_DEBUG, "failed to allocate memory for RBAC Authorization data\n"); + return false; + } + rbacData->instanceId = getZoweInstanceId(); + if (registerHttpAuthorizationHandler(server, SERVICE_AUTHORIZATION_TYPE_DEFAULT, rbacAuthorization, rbacData) == -1) { + zowelog(NULL, LOG_COMP_ID_MVD_SERVER, ZOWE_LOG_DEBUG, "failed to register RBAC authorization handler\n"); + return false; + }; + return true; +} + static void loadWebServerConfig(HttpServer *server, JsonObject *mvdSettings, JsonObject *envSettings, hashtable *htUsers, hashtable *htGroups, int defaultSessionTimeout){ MVD_SETTINGS = mvdSettings; + ENV_SETTINGS = envSettings; /* Disabled because this server is not being used by end users, but called by other servers * HttpService *mainService = makeGeneratedService("main", "/"); * mainService->serviceFunction = serveMainPage; @@ -673,6 +750,7 @@ static void installLoginService(HttpServer *server) { HttpService *httpService = makeGeneratedService("com.rs.mvd.login", "/login/**"); httpService->authType = SERVICE_AUTH_NATIVE_WITH_SESSION_TOKEN; + httpService->authorizationType = SERVICE_AUTHORIZATION_TYPE_NONE; httpService->serviceFunction = serveLoginWithSessionToken; httpService->authExtractionFunction = extractAuthorizationFromJson; registerHttpService(server, httpService); @@ -1650,6 +1728,12 @@ int main(int argc, char **argv){ ApimlStorageSettings *apimlStorageSettings = readApimlStorageSettings(slh, mvdSettings, envSettings, tlsEnv); server->defaultProductURLPrefix = PRODUCT; initializePluginIDHashTable(server); + bool rbacEnabled = isRbacEnabled(mvdSettings, envSettings); + if (!registerAuthorizationHandlers(server, rbacEnabled)) { + zowelog(NULL, LOG_COMP_ID_MVD_SERVER, ZOWE_LOG_SEVERE, ZSS_LOG_AUTH_HANDLER_REG_MSG); + zssStatus = ZSS_STATUS_ERROR; + goto out_term_stcbase; + } loadWebServerConfig(server, mvdSettings, envSettings, htUsers, htGroups, defaultSeconds); readWebPluginDefinitions(server, slh, pluginsDir, mvdSettings, envSettings, apimlStorageSettings); installCertificateService(server); @@ -1673,7 +1757,7 @@ int main(int argc, char **argv){ installAuthCheckService(server); installSecurityManagementServices(server); installOMVSService(server); - installServerStatusService(server, MVD_SETTINGS, productVersion); + installServerStatusService(server, MVD_SETTINGS, rbacEnabled, productVer); installZosPasswordService(server); installRASService(server); installUserInfoService(server); diff --git a/deps/zowe-common-c b/deps/zowe-common-c index 1f8408015..14a7225de 160000 --- a/deps/zowe-common-c +++ b/deps/zowe-common-c @@ -1 +1 @@ -Subproject commit 1f8408015b037c3c826eda1da63c6d757d56bad9 +Subproject commit 14a7225ded01fe61258235972fe74482f55fb4d8 diff --git a/h/authService.h b/h/authService.h index 6a28702f8..c687b3fc8 100644 --- a/h/authService.h +++ b/h/authService.h @@ -26,9 +26,37 @@ #include "httpserver.h" #include "dataservice.h" +#define ZOWE_SAF_CLASS "ZOWE" +#define ZOWE_PROFILE_NAME_LEN 246 + int installAuthCheckService(HttpServer *server); void installZosPasswordService(HttpServer *server); +/** + * @brief The function uses makeProfileName function to generate profile name for SAF query + * @param profileName Generated profile name goes here + * @param profileNameBufSize Size of profileName buffer including terminating '\0' + * @param parsedFile Refers to the StringList object which contains URL stripped of args + * @param instanceID Refers to instanceID for query. If none specified, or negative, then 0 + * + * @return Return non-zero if error + */ +int getProfileNameFromRequest(char *profileName, int profileNameBufSize, StringList *parsedFile, const char *method, int instanceID); + +/** + * @brief The function verifies access to a SAF profile. + * @param server HTTP Server + * @param userName Username to use in ZIS check + * @param class Class to use in ZIS check i.e. "ZOWE" + * @param entity Describes the SAF query itself i.e. "ZLUX.0.COR.GET.SERVER.AGENT.CONFIG" + * @param access Describes the access type i.e. "READ" + * @param envSettings JSON object that holds environment variables, if any + * + * @return Return non-zero if failed + */ +int verifyAccessToSafProfile(HttpServer *server, const char *userName, const char *entity, int access); + + #endif diff --git a/h/serverStatusService.h b/h/serverStatusService.h index 59649842b..55d95b804 100644 --- a/h/serverStatusService.h +++ b/h/serverStatusService.h @@ -16,9 +16,10 @@ typedef struct ServerAgentContext_tag{ char productVersion[40]; JsonObject *serverConfig; + bool rbacEnabled; } ServerAgentContext; -void installServerStatusService(HttpServer *server, JsonObject* serverSettings, char* productVer); +void installServerStatusService(HttpServer *server, JsonObject* serverSettings, bool rbacEnabled, char* productVer); #endif /* __SERVER_STATUS_H__ */ diff --git a/h/zssLogging.h b/h/zssLogging.h index 57736edfe..9da6949ae 100644 --- a/h/zssLogging.h +++ b/h/zssLogging.h @@ -513,6 +513,12 @@ bool isLogLevelValid(int level); #define ZSS_LOG_GROUP_MGMT_QRY_DEL_MSG_TEXT "Group-mgmt query string is invalid, ending request...\n" #define ZSS_LOG_GROUP_MGMT_QRY_DEL_MSG ZSS_LOG_GROUP_MGMT_QRY_DEL_MSG_ID" "ZSS_LOG_GROUP_MGMT_QRY_DEL_MSG_TEXT +#ifndef ZSS_LOG_AUTH_HANDLER_REG_MSG_ID +#define ZSS_LOG_AUTH_HANDLER_REG_MSG_ID ZSS_LOG_MSG_PRFX"1420E" +#endif +#define ZSS_LOG_AUTH_HANDLER_REG_MSG_TEXT "Failed to register authorization handlers\n" +#define ZSS_LOG_AUTH_HANDLER_REG_MSG ZSS_LOG_AUTH_HANDLER_REG_MSG_ID" "ZSS_LOG_AUTH_HANDLER_REG_MSG_TEXT + /* PassTicket */ #ifndef ZSS_LOG_PASSTICKET_GEN_FAILED_MSG_ID #define ZSS_LOG_PASSTICKET_GEN_FAILED_MSG_ID ZSS_LOG_MSG_PRFX"1500E"