From e06127566db7e6ea48c6e3d7ce7ac54f01aaf59b Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Fri, 9 Dec 2022 13:08:33 -0800 Subject: [PATCH] Add IAM API to attach/detach policies for LDAP (#16182) --- cmd/admin-handlers-idp-ldap.go | 93 +++++++++ cmd/admin-router.go | 2 +- cmd/api-errors.go | 9 + cmd/apierrorcode_string.go | 263 +++++++++++++------------- cmd/iam-store.go | 72 +++++++ cmd/iam.go | 52 +++++ cmd/typed-errors.go | 4 + go.mod | 4 +- go.sum | 8 +- internal/config/identity/ldap/ldap.go | 121 ++++++++++++ 10 files changed, 490 insertions(+), 138 deletions(-) diff --git a/cmd/admin-handlers-idp-ldap.go b/cmd/admin-handlers-idp-ldap.go index 293475163..34a0c088e 100644 --- a/cmd/admin-handlers-idp-ldap.go +++ b/cmd/admin-handlers-idp-ldap.go @@ -19,8 +19,10 @@ package cmd import ( "encoding/json" + "io" "net/http" + "github.com/gorilla/mux" "github.com/minio/madmin-go/v2" "github.com/minio/minio/internal/logger" iampolicy "github.com/minio/pkg/iam/policy" @@ -86,3 +88,94 @@ func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r } writeSuccessResponseJSON(w, econfigData) } + +// AttachDetachPolicyLDAP attaches or detaches policies from an LDAP entity +// (user or group). +// +// POST /idp/ldap/policy/{operation} +func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "AttachDetachPolicyLDAP") + + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + // Check authorization. + + objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.UpdatePolicyAssociationAction) + if objectAPI == nil { + return + } + + if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 { + // More than maxConfigSize bytes were available + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL) + return + } + + // Ensure body content type is opaque to ensure that request body has not + // been interpreted as form data. + contentType := r.Header.Get("Content-Type") + if contentType != "application/octet-stream" { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL) + return + } + + // Validate operation + operation := mux.Vars(r)["operation"] + if operation != "attach" && operation != "detach" { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL) + return + } + + isAttach := operation == "attach" + + // Validate API arguments in body. + password := cred.SecretKey + reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength)) + if err != nil { + logger.LogIf(ctx, err, logger.Application) + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL) + return + } + + var par madmin.PolicyAssociationReq + err = json.Unmarshal(reqBytes, &par) + if err != nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL) + return + } + + if err := par.IsValid(); err != nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL) + return + } + + // Call IAM subsystem + updatedAt, addedOrRemoved, err := globalIAMSys.PolicyDBUpdateLDAP(ctx, isAttach, par) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + respBody := madmin.PolicyAssociationResp{ + UpdatedAt: updatedAt, + } + if isAttach { + respBody.PoliciesAttached = addedOrRemoved + } else { + respBody.PoliciesDetached = addedOrRemoved + } + + data, err := json.Marshal(respBody) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + encryptedData, err := madmin.EncryptData(password, data) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + writeSuccessResponseJSON(w, encryptedData) +} diff --git a/cmd/admin-router.go b/cmd/admin-router.go index d00b32986..eca9e7599 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -192,7 +192,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) { // LDAP IAM operations adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListLDAPPolicyMappingEntities))) - + adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachDetachPolicyLDAP))) // -- END IAM APIs -- // GetBucketQuotaConfig diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 8199b9b33..e9b78efa2 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -265,6 +265,7 @@ const ( ErrAdminGroupNotEmpty ErrAdminNoSuchJob ErrAdminNoSuchPolicy + ErrAdminPolicyChangeAlreadyApplied ErrAdminInvalidArgument ErrAdminInvalidAccessKey ErrAdminInvalidSecretKey @@ -1245,6 +1246,12 @@ var errorCodes = errorCodeMap{ Description: "The canned policy does not exist.", HTTPStatusCode: http.StatusNotFound, }, + ErrAdminPolicyChangeAlreadyApplied: { + Code: "XMinioAdminPolicyChangeAlreadyApplied", + Description: "The specified policy change is already in effect.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminInvalidArgument: { Code: "XMinioAdminInvalidArgument", Description: "Invalid arguments specified.", @@ -1966,6 +1973,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { apiErr = ErrAdminNoSuchJob case errNoSuchPolicy: apiErr = ErrAdminNoSuchPolicy + case errNoPolicyToAttachOrDetach: + apiErr = ErrAdminPolicyChangeAlreadyApplied case errSignatureMismatch: apiErr = ErrSignatureDoesNotMatch case errInvalidRange: diff --git a/cmd/apierrorcode_string.go b/cmd/apierrorcode_string.go index f57d537a7..07554005a 100644 --- a/cmd/apierrorcode_string.go +++ b/cmd/apierrorcode_string.go @@ -180,140 +180,141 @@ func _() { _ = x[ErrAdminGroupNotEmpty-169] _ = x[ErrAdminNoSuchJob-170] _ = x[ErrAdminNoSuchPolicy-171] - _ = x[ErrAdminInvalidArgument-172] - _ = x[ErrAdminInvalidAccessKey-173] - _ = x[ErrAdminInvalidSecretKey-174] - _ = x[ErrAdminConfigNoQuorum-175] - _ = x[ErrAdminConfigTooLarge-176] - _ = x[ErrAdminConfigBadJSON-177] - _ = x[ErrAdminNoSuchConfigTarget-178] - _ = x[ErrAdminConfigEnvOverridden-179] - _ = x[ErrAdminConfigDuplicateKeys-180] - _ = x[ErrAdminConfigInvalidIDPType-181] - _ = x[ErrAdminConfigLDAPValidation-182] - _ = x[ErrAdminConfigIDPCfgNameAlreadyExists-183] - _ = x[ErrAdminConfigIDPCfgNameDoesNotExist-184] - _ = x[ErrAdminCredentialsMismatch-185] - _ = x[ErrInsecureClientRequest-186] - _ = x[ErrObjectTampered-187] - _ = x[ErrSiteReplicationInvalidRequest-188] - _ = x[ErrSiteReplicationPeerResp-189] - _ = x[ErrSiteReplicationBackendIssue-190] - _ = x[ErrSiteReplicationServiceAccountError-191] - _ = x[ErrSiteReplicationBucketConfigError-192] - _ = x[ErrSiteReplicationBucketMetaError-193] - _ = x[ErrSiteReplicationIAMError-194] - _ = x[ErrSiteReplicationConfigMissing-195] - _ = x[ErrAdminRebalanceAlreadyStarted-196] - _ = x[ErrAdminRebalanceNotStarted-197] - _ = x[ErrAdminBucketQuotaExceeded-198] - _ = x[ErrAdminNoSuchQuotaConfiguration-199] - _ = x[ErrHealNotImplemented-200] - _ = x[ErrHealNoSuchProcess-201] - _ = x[ErrHealInvalidClientToken-202] - _ = x[ErrHealMissingBucket-203] - _ = x[ErrHealAlreadyRunning-204] - _ = x[ErrHealOverlappingPaths-205] - _ = x[ErrIncorrectContinuationToken-206] - _ = x[ErrEmptyRequestBody-207] - _ = x[ErrUnsupportedFunction-208] - _ = x[ErrInvalidExpressionType-209] - _ = x[ErrBusy-210] - _ = x[ErrUnauthorizedAccess-211] - _ = x[ErrExpressionTooLong-212] - _ = x[ErrIllegalSQLFunctionArgument-213] - _ = x[ErrInvalidKeyPath-214] - _ = x[ErrInvalidCompressionFormat-215] - _ = x[ErrInvalidFileHeaderInfo-216] - _ = x[ErrInvalidJSONType-217] - _ = x[ErrInvalidQuoteFields-218] - _ = x[ErrInvalidRequestParameter-219] - _ = x[ErrInvalidDataType-220] - _ = x[ErrInvalidTextEncoding-221] - _ = x[ErrInvalidDataSource-222] - _ = x[ErrInvalidTableAlias-223] - _ = x[ErrMissingRequiredParameter-224] - _ = x[ErrObjectSerializationConflict-225] - _ = x[ErrUnsupportedSQLOperation-226] - _ = x[ErrUnsupportedSQLStructure-227] - _ = x[ErrUnsupportedSyntax-228] - _ = x[ErrUnsupportedRangeHeader-229] - _ = x[ErrLexerInvalidChar-230] - _ = x[ErrLexerInvalidOperator-231] - _ = x[ErrLexerInvalidLiteral-232] - _ = x[ErrLexerInvalidIONLiteral-233] - _ = x[ErrParseExpectedDatePart-234] - _ = x[ErrParseExpectedKeyword-235] - _ = x[ErrParseExpectedTokenType-236] - _ = x[ErrParseExpected2TokenTypes-237] - _ = x[ErrParseExpectedNumber-238] - _ = x[ErrParseExpectedRightParenBuiltinFunctionCall-239] - _ = x[ErrParseExpectedTypeName-240] - _ = x[ErrParseExpectedWhenClause-241] - _ = x[ErrParseUnsupportedToken-242] - _ = x[ErrParseUnsupportedLiteralsGroupBy-243] - _ = x[ErrParseExpectedMember-244] - _ = x[ErrParseUnsupportedSelect-245] - _ = x[ErrParseUnsupportedCase-246] - _ = x[ErrParseUnsupportedCaseClause-247] - _ = x[ErrParseUnsupportedAlias-248] - _ = x[ErrParseUnsupportedSyntax-249] - _ = x[ErrParseUnknownOperator-250] - _ = x[ErrParseMissingIdentAfterAt-251] - _ = x[ErrParseUnexpectedOperator-252] - _ = x[ErrParseUnexpectedTerm-253] - _ = x[ErrParseUnexpectedToken-254] - _ = x[ErrParseUnexpectedKeyword-255] - _ = x[ErrParseExpectedExpression-256] - _ = x[ErrParseExpectedLeftParenAfterCast-257] - _ = x[ErrParseExpectedLeftParenValueConstructor-258] - _ = x[ErrParseExpectedLeftParenBuiltinFunctionCall-259] - _ = x[ErrParseExpectedArgumentDelimiter-260] - _ = x[ErrParseCastArity-261] - _ = x[ErrParseInvalidTypeParam-262] - _ = x[ErrParseEmptySelect-263] - _ = x[ErrParseSelectMissingFrom-264] - _ = x[ErrParseExpectedIdentForGroupName-265] - _ = x[ErrParseExpectedIdentForAlias-266] - _ = x[ErrParseUnsupportedCallWithStar-267] - _ = x[ErrParseNonUnaryAgregateFunctionCall-268] - _ = x[ErrParseMalformedJoin-269] - _ = x[ErrParseExpectedIdentForAt-270] - _ = x[ErrParseAsteriskIsNotAloneInSelectList-271] - _ = x[ErrParseCannotMixSqbAndWildcardInSelectList-272] - _ = x[ErrParseInvalidContextForWildcardInSelectList-273] - _ = x[ErrIncorrectSQLFunctionArgumentType-274] - _ = x[ErrValueParseFailure-275] - _ = x[ErrEvaluatorInvalidArguments-276] - _ = x[ErrIntegerOverflow-277] - _ = x[ErrLikeInvalidInputs-278] - _ = x[ErrCastFailed-279] - _ = x[ErrInvalidCast-280] - _ = x[ErrEvaluatorInvalidTimestampFormatPattern-281] - _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing-282] - _ = x[ErrEvaluatorTimestampFormatPatternDuplicateFields-283] - _ = x[ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch-284] - _ = x[ErrEvaluatorUnterminatedTimestampFormatPatternToken-285] - _ = x[ErrEvaluatorInvalidTimestampFormatPatternToken-286] - _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbol-287] - _ = x[ErrEvaluatorBindingDoesNotExist-288] - _ = x[ErrMissingHeaders-289] - _ = x[ErrInvalidColumnIndex-290] - _ = x[ErrAdminConfigNotificationTargetsFailed-291] - _ = x[ErrAdminProfilerNotEnabled-292] - _ = x[ErrInvalidDecompressedSize-293] - _ = x[ErrAddUserInvalidArgument-294] - _ = x[ErrAdminResourceInvalidArgument-295] - _ = x[ErrAdminAccountNotEligible-296] - _ = x[ErrAccountNotEligible-297] - _ = x[ErrAdminServiceAccountNotFound-298] - _ = x[ErrPostPolicyConditionInvalidFormat-299] - _ = x[ErrInvalidChecksum-300] + _ = x[ErrAdminPolicyChangeAlreadyApplied-172] + _ = x[ErrAdminInvalidArgument-173] + _ = x[ErrAdminInvalidAccessKey-174] + _ = x[ErrAdminInvalidSecretKey-175] + _ = x[ErrAdminConfigNoQuorum-176] + _ = x[ErrAdminConfigTooLarge-177] + _ = x[ErrAdminConfigBadJSON-178] + _ = x[ErrAdminNoSuchConfigTarget-179] + _ = x[ErrAdminConfigEnvOverridden-180] + _ = x[ErrAdminConfigDuplicateKeys-181] + _ = x[ErrAdminConfigInvalidIDPType-182] + _ = x[ErrAdminConfigLDAPValidation-183] + _ = x[ErrAdminConfigIDPCfgNameAlreadyExists-184] + _ = x[ErrAdminConfigIDPCfgNameDoesNotExist-185] + _ = x[ErrAdminCredentialsMismatch-186] + _ = x[ErrInsecureClientRequest-187] + _ = x[ErrObjectTampered-188] + _ = x[ErrSiteReplicationInvalidRequest-189] + _ = x[ErrSiteReplicationPeerResp-190] + _ = x[ErrSiteReplicationBackendIssue-191] + _ = x[ErrSiteReplicationServiceAccountError-192] + _ = x[ErrSiteReplicationBucketConfigError-193] + _ = x[ErrSiteReplicationBucketMetaError-194] + _ = x[ErrSiteReplicationIAMError-195] + _ = x[ErrSiteReplicationConfigMissing-196] + _ = x[ErrAdminRebalanceAlreadyStarted-197] + _ = x[ErrAdminRebalanceNotStarted-198] + _ = x[ErrAdminBucketQuotaExceeded-199] + _ = x[ErrAdminNoSuchQuotaConfiguration-200] + _ = x[ErrHealNotImplemented-201] + _ = x[ErrHealNoSuchProcess-202] + _ = x[ErrHealInvalidClientToken-203] + _ = x[ErrHealMissingBucket-204] + _ = x[ErrHealAlreadyRunning-205] + _ = x[ErrHealOverlappingPaths-206] + _ = x[ErrIncorrectContinuationToken-207] + _ = x[ErrEmptyRequestBody-208] + _ = x[ErrUnsupportedFunction-209] + _ = x[ErrInvalidExpressionType-210] + _ = x[ErrBusy-211] + _ = x[ErrUnauthorizedAccess-212] + _ = x[ErrExpressionTooLong-213] + _ = x[ErrIllegalSQLFunctionArgument-214] + _ = x[ErrInvalidKeyPath-215] + _ = x[ErrInvalidCompressionFormat-216] + _ = x[ErrInvalidFileHeaderInfo-217] + _ = x[ErrInvalidJSONType-218] + _ = x[ErrInvalidQuoteFields-219] + _ = x[ErrInvalidRequestParameter-220] + _ = x[ErrInvalidDataType-221] + _ = x[ErrInvalidTextEncoding-222] + _ = x[ErrInvalidDataSource-223] + _ = x[ErrInvalidTableAlias-224] + _ = x[ErrMissingRequiredParameter-225] + _ = x[ErrObjectSerializationConflict-226] + _ = x[ErrUnsupportedSQLOperation-227] + _ = x[ErrUnsupportedSQLStructure-228] + _ = x[ErrUnsupportedSyntax-229] + _ = x[ErrUnsupportedRangeHeader-230] + _ = x[ErrLexerInvalidChar-231] + _ = x[ErrLexerInvalidOperator-232] + _ = x[ErrLexerInvalidLiteral-233] + _ = x[ErrLexerInvalidIONLiteral-234] + _ = x[ErrParseExpectedDatePart-235] + _ = x[ErrParseExpectedKeyword-236] + _ = x[ErrParseExpectedTokenType-237] + _ = x[ErrParseExpected2TokenTypes-238] + _ = x[ErrParseExpectedNumber-239] + _ = x[ErrParseExpectedRightParenBuiltinFunctionCall-240] + _ = x[ErrParseExpectedTypeName-241] + _ = x[ErrParseExpectedWhenClause-242] + _ = x[ErrParseUnsupportedToken-243] + _ = x[ErrParseUnsupportedLiteralsGroupBy-244] + _ = x[ErrParseExpectedMember-245] + _ = x[ErrParseUnsupportedSelect-246] + _ = x[ErrParseUnsupportedCase-247] + _ = x[ErrParseUnsupportedCaseClause-248] + _ = x[ErrParseUnsupportedAlias-249] + _ = x[ErrParseUnsupportedSyntax-250] + _ = x[ErrParseUnknownOperator-251] + _ = x[ErrParseMissingIdentAfterAt-252] + _ = x[ErrParseUnexpectedOperator-253] + _ = x[ErrParseUnexpectedTerm-254] + _ = x[ErrParseUnexpectedToken-255] + _ = x[ErrParseUnexpectedKeyword-256] + _ = x[ErrParseExpectedExpression-257] + _ = x[ErrParseExpectedLeftParenAfterCast-258] + _ = x[ErrParseExpectedLeftParenValueConstructor-259] + _ = x[ErrParseExpectedLeftParenBuiltinFunctionCall-260] + _ = x[ErrParseExpectedArgumentDelimiter-261] + _ = x[ErrParseCastArity-262] + _ = x[ErrParseInvalidTypeParam-263] + _ = x[ErrParseEmptySelect-264] + _ = x[ErrParseSelectMissingFrom-265] + _ = x[ErrParseExpectedIdentForGroupName-266] + _ = x[ErrParseExpectedIdentForAlias-267] + _ = x[ErrParseUnsupportedCallWithStar-268] + _ = x[ErrParseNonUnaryAgregateFunctionCall-269] + _ = x[ErrParseMalformedJoin-270] + _ = x[ErrParseExpectedIdentForAt-271] + _ = x[ErrParseAsteriskIsNotAloneInSelectList-272] + _ = x[ErrParseCannotMixSqbAndWildcardInSelectList-273] + _ = x[ErrParseInvalidContextForWildcardInSelectList-274] + _ = x[ErrIncorrectSQLFunctionArgumentType-275] + _ = x[ErrValueParseFailure-276] + _ = x[ErrEvaluatorInvalidArguments-277] + _ = x[ErrIntegerOverflow-278] + _ = x[ErrLikeInvalidInputs-279] + _ = x[ErrCastFailed-280] + _ = x[ErrInvalidCast-281] + _ = x[ErrEvaluatorInvalidTimestampFormatPattern-282] + _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing-283] + _ = x[ErrEvaluatorTimestampFormatPatternDuplicateFields-284] + _ = x[ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch-285] + _ = x[ErrEvaluatorUnterminatedTimestampFormatPatternToken-286] + _ = x[ErrEvaluatorInvalidTimestampFormatPatternToken-287] + _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbol-288] + _ = x[ErrEvaluatorBindingDoesNotExist-289] + _ = x[ErrMissingHeaders-290] + _ = x[ErrInvalidColumnIndex-291] + _ = x[ErrAdminConfigNotificationTargetsFailed-292] + _ = x[ErrAdminProfilerNotEnabled-293] + _ = x[ErrInvalidDecompressedSize-294] + _ = x[ErrAddUserInvalidArgument-295] + _ = x[ErrAdminResourceInvalidArgument-296] + _ = x[ErrAdminAccountNotEligible-297] + _ = x[ErrAccountNotEligible-298] + _ = x[ErrAdminServiceAccountNotFound-299] + _ = x[ErrPostPolicyConditionInvalidFormat-300] + _ = x[ErrInvalidChecksum-301] } -const _APIErrorCode_name = "NoneAccessDeniedBadDigestEntityTooSmallEntityTooLargePolicyTooLargeIncompleteBodyInternalErrorInvalidAccessKeyIDAccessKeyDisabledInvalidBucketNameInvalidDigestInvalidRangeInvalidRangePartNumberInvalidCopyPartRangeInvalidCopyPartRangeSourceInvalidMaxKeysInvalidEncodingMethodInvalidMaxUploadsInvalidMaxPartsInvalidPartNumberMarkerInvalidPartNumberInvalidRequestBodyInvalidCopySourceInvalidMetadataDirectiveInvalidCopyDestInvalidPolicyDocumentInvalidObjectStateMalformedXMLMissingContentLengthMissingContentMD5MissingRequestBodyErrorMissingSecurityHeaderNoSuchBucketNoSuchBucketPolicyNoSuchBucketLifecycleNoSuchLifecycleConfigurationInvalidLifecycleWithObjectLockNoSuchBucketSSEConfigNoSuchCORSConfigurationNoSuchWebsiteConfigurationReplicationConfigurationNotFoundErrorRemoteDestinationNotFoundErrorReplicationDestinationMissingLockRemoteTargetNotFoundErrorReplicationRemoteConnectionErrorReplicationBandwidthLimitErrorBucketRemoteIdenticalToSourceBucketRemoteAlreadyExistsBucketRemoteLabelInUseBucketRemoteArnTypeInvalidBucketRemoteArnInvalidBucketRemoteRemoveDisallowedRemoteTargetNotVersionedErrorReplicationSourceNotVersionedErrorReplicationNeedsVersioningErrorReplicationBucketNeedsVersioningErrorReplicationDenyEditErrorReplicationNoExistingObjectsObjectRestoreAlreadyInProgressNoSuchKeyNoSuchUploadInvalidVersionIDNoSuchVersionNotImplementedPreconditionFailedRequestTimeTooSkewedSignatureDoesNotMatchMethodNotAllowedInvalidPartInvalidPartOrderAuthorizationHeaderMalformedMalformedPOSTRequestPOSTFileRequiredSignatureVersionNotSupportedBucketNotEmptyAllAccessDisabledPolicyInvalidVersionMissingFieldsMissingCredTagCredMalformedInvalidRegionInvalidServiceS3InvalidServiceSTSInvalidRequestVersionMissingSignTagMissingSignHeadersTagMalformedDateMalformedPresignedDateMalformedCredentialDateMalformedCredentialRegionMalformedExpiresNegativeExpiresAuthHeaderEmptyExpiredPresignRequestRequestNotReadyYetUnsignedHeadersMissingDateHeaderInvalidQuerySignatureAlgoInvalidQueryParamsBucketAlreadyOwnedByYouInvalidDurationBucketAlreadyExistsMetadataTooLargeUnsupportedMetadataMaximumExpiresSlowDownInvalidPrefixMarkerBadRequestKeyTooLongErrorInvalidBucketObjectLockConfigurationObjectLockConfigurationNotFoundObjectLockConfigurationNotAllowedNoSuchObjectLockConfigurationObjectLockedInvalidRetentionDatePastObjectLockRetainDateUnknownWORMModeDirectiveBucketTaggingNotFoundObjectLockInvalidHeadersInvalidTagDirectiveInvalidEncryptionMethodInvalidEncryptionKeyIDInsecureSSECustomerRequestSSEMultipartEncryptedSSEEncryptedObjectInvalidEncryptionParametersInvalidSSECustomerAlgorithmInvalidSSECustomerKeyMissingSSECustomerKeyMissingSSECustomerKeyMD5SSECustomerKeyMD5MismatchInvalidSSECustomerParametersIncompatibleEncryptionMethodKMSNotConfiguredKMSKeyNotFoundExceptionNoAccessKeyInvalidTokenEventNotificationARNNotificationRegionNotificationOverlappingFilterNotificationFilterNameInvalidFilterNamePrefixFilterNameSuffixFilterValueInvalidOverlappingConfigsUnsupportedNotificationContentSHA256MismatchContentChecksumMismatchReadQuorumWriteQuorumStorageFullRequestBodyParseObjectExistsAsDirectoryInvalidObjectNameInvalidObjectNamePrefixSlashInvalidResourceNameServerNotInitializedOperationTimedOutClientDisconnectedOperationMaxedOutInvalidRequestTransitionStorageClassNotFoundErrorInvalidStorageClassBackendDownMalformedJSONAdminNoSuchUserAdminNoSuchGroupAdminGroupNotEmptyAdminNoSuchJobAdminNoSuchPolicyAdminInvalidArgumentAdminInvalidAccessKeyAdminInvalidSecretKeyAdminConfigNoQuorumAdminConfigTooLargeAdminConfigBadJSONAdminNoSuchConfigTargetAdminConfigEnvOverriddenAdminConfigDuplicateKeysAdminConfigInvalidIDPTypeAdminConfigLDAPValidationAdminConfigIDPCfgNameAlreadyExistsAdminConfigIDPCfgNameDoesNotExistAdminCredentialsMismatchInsecureClientRequestObjectTamperedSiteReplicationInvalidRequestSiteReplicationPeerRespSiteReplicationBackendIssueSiteReplicationServiceAccountErrorSiteReplicationBucketConfigErrorSiteReplicationBucketMetaErrorSiteReplicationIAMErrorSiteReplicationConfigMissingAdminRebalanceAlreadyStartedAdminRebalanceNotStartedAdminBucketQuotaExceededAdminNoSuchQuotaConfigurationHealNotImplementedHealNoSuchProcessHealInvalidClientTokenHealMissingBucketHealAlreadyRunningHealOverlappingPathsIncorrectContinuationTokenEmptyRequestBodyUnsupportedFunctionInvalidExpressionTypeBusyUnauthorizedAccessExpressionTooLongIllegalSQLFunctionArgumentInvalidKeyPathInvalidCompressionFormatInvalidFileHeaderInfoInvalidJSONTypeInvalidQuoteFieldsInvalidRequestParameterInvalidDataTypeInvalidTextEncodingInvalidDataSourceInvalidTableAliasMissingRequiredParameterObjectSerializationConflictUnsupportedSQLOperationUnsupportedSQLStructureUnsupportedSyntaxUnsupportedRangeHeaderLexerInvalidCharLexerInvalidOperatorLexerInvalidLiteralLexerInvalidIONLiteralParseExpectedDatePartParseExpectedKeywordParseExpectedTokenTypeParseExpected2TokenTypesParseExpectedNumberParseExpectedRightParenBuiltinFunctionCallParseExpectedTypeNameParseExpectedWhenClauseParseUnsupportedTokenParseUnsupportedLiteralsGroupByParseExpectedMemberParseUnsupportedSelectParseUnsupportedCaseParseUnsupportedCaseClauseParseUnsupportedAliasParseUnsupportedSyntaxParseUnknownOperatorParseMissingIdentAfterAtParseUnexpectedOperatorParseUnexpectedTermParseUnexpectedTokenParseUnexpectedKeywordParseExpectedExpressionParseExpectedLeftParenAfterCastParseExpectedLeftParenValueConstructorParseExpectedLeftParenBuiltinFunctionCallParseExpectedArgumentDelimiterParseCastArityParseInvalidTypeParamParseEmptySelectParseSelectMissingFromParseExpectedIdentForGroupNameParseExpectedIdentForAliasParseUnsupportedCallWithStarParseNonUnaryAgregateFunctionCallParseMalformedJoinParseExpectedIdentForAtParseAsteriskIsNotAloneInSelectListParseCannotMixSqbAndWildcardInSelectListParseInvalidContextForWildcardInSelectListIncorrectSQLFunctionArgumentTypeValueParseFailureEvaluatorInvalidArgumentsIntegerOverflowLikeInvalidInputsCastFailedInvalidCastEvaluatorInvalidTimestampFormatPatternEvaluatorInvalidTimestampFormatPatternSymbolForParsingEvaluatorTimestampFormatPatternDuplicateFieldsEvaluatorTimestampFormatPatternHourClockAmPmMismatchEvaluatorUnterminatedTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternSymbolEvaluatorBindingDoesNotExistMissingHeadersInvalidColumnIndexAdminConfigNotificationTargetsFailedAdminProfilerNotEnabledInvalidDecompressedSizeAddUserInvalidArgumentAdminResourceInvalidArgumentAdminAccountNotEligibleAccountNotEligibleAdminServiceAccountNotFoundPostPolicyConditionInvalidFormatInvalidChecksum" +const _APIErrorCode_name = "NoneAccessDeniedBadDigestEntityTooSmallEntityTooLargePolicyTooLargeIncompleteBodyInternalErrorInvalidAccessKeyIDAccessKeyDisabledInvalidBucketNameInvalidDigestInvalidRangeInvalidRangePartNumberInvalidCopyPartRangeInvalidCopyPartRangeSourceInvalidMaxKeysInvalidEncodingMethodInvalidMaxUploadsInvalidMaxPartsInvalidPartNumberMarkerInvalidPartNumberInvalidRequestBodyInvalidCopySourceInvalidMetadataDirectiveInvalidCopyDestInvalidPolicyDocumentInvalidObjectStateMalformedXMLMissingContentLengthMissingContentMD5MissingRequestBodyErrorMissingSecurityHeaderNoSuchBucketNoSuchBucketPolicyNoSuchBucketLifecycleNoSuchLifecycleConfigurationInvalidLifecycleWithObjectLockNoSuchBucketSSEConfigNoSuchCORSConfigurationNoSuchWebsiteConfigurationReplicationConfigurationNotFoundErrorRemoteDestinationNotFoundErrorReplicationDestinationMissingLockRemoteTargetNotFoundErrorReplicationRemoteConnectionErrorReplicationBandwidthLimitErrorBucketRemoteIdenticalToSourceBucketRemoteAlreadyExistsBucketRemoteLabelInUseBucketRemoteArnTypeInvalidBucketRemoteArnInvalidBucketRemoteRemoveDisallowedRemoteTargetNotVersionedErrorReplicationSourceNotVersionedErrorReplicationNeedsVersioningErrorReplicationBucketNeedsVersioningErrorReplicationDenyEditErrorReplicationNoExistingObjectsObjectRestoreAlreadyInProgressNoSuchKeyNoSuchUploadInvalidVersionIDNoSuchVersionNotImplementedPreconditionFailedRequestTimeTooSkewedSignatureDoesNotMatchMethodNotAllowedInvalidPartInvalidPartOrderAuthorizationHeaderMalformedMalformedPOSTRequestPOSTFileRequiredSignatureVersionNotSupportedBucketNotEmptyAllAccessDisabledPolicyInvalidVersionMissingFieldsMissingCredTagCredMalformedInvalidRegionInvalidServiceS3InvalidServiceSTSInvalidRequestVersionMissingSignTagMissingSignHeadersTagMalformedDateMalformedPresignedDateMalformedCredentialDateMalformedCredentialRegionMalformedExpiresNegativeExpiresAuthHeaderEmptyExpiredPresignRequestRequestNotReadyYetUnsignedHeadersMissingDateHeaderInvalidQuerySignatureAlgoInvalidQueryParamsBucketAlreadyOwnedByYouInvalidDurationBucketAlreadyExistsMetadataTooLargeUnsupportedMetadataMaximumExpiresSlowDownInvalidPrefixMarkerBadRequestKeyTooLongErrorInvalidBucketObjectLockConfigurationObjectLockConfigurationNotFoundObjectLockConfigurationNotAllowedNoSuchObjectLockConfigurationObjectLockedInvalidRetentionDatePastObjectLockRetainDateUnknownWORMModeDirectiveBucketTaggingNotFoundObjectLockInvalidHeadersInvalidTagDirectiveInvalidEncryptionMethodInvalidEncryptionKeyIDInsecureSSECustomerRequestSSEMultipartEncryptedSSEEncryptedObjectInvalidEncryptionParametersInvalidSSECustomerAlgorithmInvalidSSECustomerKeyMissingSSECustomerKeyMissingSSECustomerKeyMD5SSECustomerKeyMD5MismatchInvalidSSECustomerParametersIncompatibleEncryptionMethodKMSNotConfiguredKMSKeyNotFoundExceptionNoAccessKeyInvalidTokenEventNotificationARNNotificationRegionNotificationOverlappingFilterNotificationFilterNameInvalidFilterNamePrefixFilterNameSuffixFilterValueInvalidOverlappingConfigsUnsupportedNotificationContentSHA256MismatchContentChecksumMismatchReadQuorumWriteQuorumStorageFullRequestBodyParseObjectExistsAsDirectoryInvalidObjectNameInvalidObjectNamePrefixSlashInvalidResourceNameServerNotInitializedOperationTimedOutClientDisconnectedOperationMaxedOutInvalidRequestTransitionStorageClassNotFoundErrorInvalidStorageClassBackendDownMalformedJSONAdminNoSuchUserAdminNoSuchGroupAdminGroupNotEmptyAdminNoSuchJobAdminNoSuchPolicyAdminPolicyChangeAlreadyAppliedAdminInvalidArgumentAdminInvalidAccessKeyAdminInvalidSecretKeyAdminConfigNoQuorumAdminConfigTooLargeAdminConfigBadJSONAdminNoSuchConfigTargetAdminConfigEnvOverriddenAdminConfigDuplicateKeysAdminConfigInvalidIDPTypeAdminConfigLDAPValidationAdminConfigIDPCfgNameAlreadyExistsAdminConfigIDPCfgNameDoesNotExistAdminCredentialsMismatchInsecureClientRequestObjectTamperedSiteReplicationInvalidRequestSiteReplicationPeerRespSiteReplicationBackendIssueSiteReplicationServiceAccountErrorSiteReplicationBucketConfigErrorSiteReplicationBucketMetaErrorSiteReplicationIAMErrorSiteReplicationConfigMissingAdminRebalanceAlreadyStartedAdminRebalanceNotStartedAdminBucketQuotaExceededAdminNoSuchQuotaConfigurationHealNotImplementedHealNoSuchProcessHealInvalidClientTokenHealMissingBucketHealAlreadyRunningHealOverlappingPathsIncorrectContinuationTokenEmptyRequestBodyUnsupportedFunctionInvalidExpressionTypeBusyUnauthorizedAccessExpressionTooLongIllegalSQLFunctionArgumentInvalidKeyPathInvalidCompressionFormatInvalidFileHeaderInfoInvalidJSONTypeInvalidQuoteFieldsInvalidRequestParameterInvalidDataTypeInvalidTextEncodingInvalidDataSourceInvalidTableAliasMissingRequiredParameterObjectSerializationConflictUnsupportedSQLOperationUnsupportedSQLStructureUnsupportedSyntaxUnsupportedRangeHeaderLexerInvalidCharLexerInvalidOperatorLexerInvalidLiteralLexerInvalidIONLiteralParseExpectedDatePartParseExpectedKeywordParseExpectedTokenTypeParseExpected2TokenTypesParseExpectedNumberParseExpectedRightParenBuiltinFunctionCallParseExpectedTypeNameParseExpectedWhenClauseParseUnsupportedTokenParseUnsupportedLiteralsGroupByParseExpectedMemberParseUnsupportedSelectParseUnsupportedCaseParseUnsupportedCaseClauseParseUnsupportedAliasParseUnsupportedSyntaxParseUnknownOperatorParseMissingIdentAfterAtParseUnexpectedOperatorParseUnexpectedTermParseUnexpectedTokenParseUnexpectedKeywordParseExpectedExpressionParseExpectedLeftParenAfterCastParseExpectedLeftParenValueConstructorParseExpectedLeftParenBuiltinFunctionCallParseExpectedArgumentDelimiterParseCastArityParseInvalidTypeParamParseEmptySelectParseSelectMissingFromParseExpectedIdentForGroupNameParseExpectedIdentForAliasParseUnsupportedCallWithStarParseNonUnaryAgregateFunctionCallParseMalformedJoinParseExpectedIdentForAtParseAsteriskIsNotAloneInSelectListParseCannotMixSqbAndWildcardInSelectListParseInvalidContextForWildcardInSelectListIncorrectSQLFunctionArgumentTypeValueParseFailureEvaluatorInvalidArgumentsIntegerOverflowLikeInvalidInputsCastFailedInvalidCastEvaluatorInvalidTimestampFormatPatternEvaluatorInvalidTimestampFormatPatternSymbolForParsingEvaluatorTimestampFormatPatternDuplicateFieldsEvaluatorTimestampFormatPatternHourClockAmPmMismatchEvaluatorUnterminatedTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternSymbolEvaluatorBindingDoesNotExistMissingHeadersInvalidColumnIndexAdminConfigNotificationTargetsFailedAdminProfilerNotEnabledInvalidDecompressedSizeAddUserInvalidArgumentAdminResourceInvalidArgumentAdminAccountNotEligibleAccountNotEligibleAdminServiceAccountNotFoundPostPolicyConditionInvalidFormatInvalidChecksum" -var _APIErrorCode_index = [...]uint16{0, 4, 16, 25, 39, 53, 67, 81, 94, 112, 129, 146, 159, 171, 193, 213, 239, 253, 274, 291, 306, 329, 346, 364, 381, 405, 420, 441, 459, 471, 491, 508, 531, 552, 564, 582, 603, 631, 661, 682, 705, 731, 768, 798, 831, 856, 888, 918, 947, 972, 994, 1020, 1042, 1070, 1099, 1133, 1164, 1201, 1225, 1253, 1283, 1292, 1304, 1320, 1333, 1347, 1365, 1385, 1406, 1422, 1433, 1449, 1477, 1497, 1513, 1541, 1555, 1572, 1592, 1605, 1619, 1632, 1645, 1661, 1678, 1699, 1713, 1734, 1747, 1769, 1792, 1817, 1833, 1848, 1863, 1884, 1902, 1917, 1934, 1959, 1977, 2000, 2015, 2034, 2050, 2069, 2083, 2091, 2110, 2120, 2135, 2171, 2202, 2235, 2264, 2276, 2296, 2320, 2344, 2365, 2389, 2408, 2431, 2453, 2479, 2500, 2518, 2545, 2572, 2593, 2614, 2638, 2663, 2691, 2719, 2735, 2758, 2769, 2781, 2798, 2813, 2831, 2860, 2877, 2893, 2909, 2927, 2945, 2968, 2989, 3012, 3022, 3033, 3044, 3060, 3083, 3100, 3128, 3147, 3167, 3184, 3202, 3219, 3233, 3268, 3287, 3298, 3311, 3326, 3342, 3360, 3374, 3391, 3411, 3432, 3453, 3472, 3491, 3509, 3532, 3556, 3580, 3605, 3630, 3664, 3697, 3721, 3742, 3756, 3785, 3808, 3835, 3869, 3901, 3931, 3954, 3982, 4010, 4034, 4058, 4087, 4105, 4122, 4144, 4161, 4179, 4199, 4225, 4241, 4260, 4281, 4285, 4303, 4320, 4346, 4360, 4384, 4405, 4420, 4438, 4461, 4476, 4495, 4512, 4529, 4553, 4580, 4603, 4626, 4643, 4665, 4681, 4701, 4720, 4742, 4763, 4783, 4805, 4829, 4848, 4890, 4911, 4934, 4955, 4986, 5005, 5027, 5047, 5073, 5094, 5116, 5136, 5160, 5183, 5202, 5222, 5244, 5267, 5298, 5336, 5377, 5407, 5421, 5442, 5458, 5480, 5510, 5536, 5564, 5597, 5615, 5638, 5673, 5713, 5755, 5787, 5804, 5829, 5844, 5861, 5871, 5882, 5920, 5974, 6020, 6072, 6120, 6163, 6207, 6235, 6249, 6267, 6303, 6326, 6349, 6371, 6399, 6422, 6440, 6467, 6499, 6514} +var _APIErrorCode_index = [...]uint16{0, 4, 16, 25, 39, 53, 67, 81, 94, 112, 129, 146, 159, 171, 193, 213, 239, 253, 274, 291, 306, 329, 346, 364, 381, 405, 420, 441, 459, 471, 491, 508, 531, 552, 564, 582, 603, 631, 661, 682, 705, 731, 768, 798, 831, 856, 888, 918, 947, 972, 994, 1020, 1042, 1070, 1099, 1133, 1164, 1201, 1225, 1253, 1283, 1292, 1304, 1320, 1333, 1347, 1365, 1385, 1406, 1422, 1433, 1449, 1477, 1497, 1513, 1541, 1555, 1572, 1592, 1605, 1619, 1632, 1645, 1661, 1678, 1699, 1713, 1734, 1747, 1769, 1792, 1817, 1833, 1848, 1863, 1884, 1902, 1917, 1934, 1959, 1977, 2000, 2015, 2034, 2050, 2069, 2083, 2091, 2110, 2120, 2135, 2171, 2202, 2235, 2264, 2276, 2296, 2320, 2344, 2365, 2389, 2408, 2431, 2453, 2479, 2500, 2518, 2545, 2572, 2593, 2614, 2638, 2663, 2691, 2719, 2735, 2758, 2769, 2781, 2798, 2813, 2831, 2860, 2877, 2893, 2909, 2927, 2945, 2968, 2989, 3012, 3022, 3033, 3044, 3060, 3083, 3100, 3128, 3147, 3167, 3184, 3202, 3219, 3233, 3268, 3287, 3298, 3311, 3326, 3342, 3360, 3374, 3391, 3422, 3442, 3463, 3484, 3503, 3522, 3540, 3563, 3587, 3611, 3636, 3661, 3695, 3728, 3752, 3773, 3787, 3816, 3839, 3866, 3900, 3932, 3962, 3985, 4013, 4041, 4065, 4089, 4118, 4136, 4153, 4175, 4192, 4210, 4230, 4256, 4272, 4291, 4312, 4316, 4334, 4351, 4377, 4391, 4415, 4436, 4451, 4469, 4492, 4507, 4526, 4543, 4560, 4584, 4611, 4634, 4657, 4674, 4696, 4712, 4732, 4751, 4773, 4794, 4814, 4836, 4860, 4879, 4921, 4942, 4965, 4986, 5017, 5036, 5058, 5078, 5104, 5125, 5147, 5167, 5191, 5214, 5233, 5253, 5275, 5298, 5329, 5367, 5408, 5438, 5452, 5473, 5489, 5511, 5541, 5567, 5595, 5628, 5646, 5669, 5704, 5744, 5786, 5818, 5835, 5860, 5875, 5892, 5902, 5913, 5951, 6005, 6051, 6103, 6151, 6194, 6238, 6266, 6280, 6298, 6334, 6357, 6380, 6402, 6430, 6453, 6471, 6498, 6530, 6545} func (i APIErrorCode) String() string { if i < 0 || i >= APIErrorCode(len(_APIErrorCode_index)-1) { diff --git a/cmd/iam-store.go b/cmd/iam-store.go index 1269327a1..382d25c01 100644 --- a/cmd/iam-store.go +++ b/cmd/iam-store.go @@ -862,6 +862,78 @@ func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err err return } +// PolicyDBUpdate - adds or removes given policies to/from the user or group's +// policy associations. +func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGroup bool, + userType IAMUserType, policies []string, isAttach bool) (updatedAt time.Time, addedOrRemoved []string, + err error, +) { + if name == "" { + return updatedAt, nil, errInvalidArgument + } + + cache := store.lock() + defer store.unlock() + + // Load existing policy mapping + var mp MappedPolicy + if !isGroup { + mp = cache.iamUserPolicyMap[name] + } else { + if store.getUsersSysType() == MinIOUsersSysType { + g, ok := cache.iamGroupsMap[name] + if !ok { + return updatedAt, nil, errNoSuchGroup + } + + if g.Status == statusDisabled { + // TODO: return an error? + return updatedAt, nil, nil + } + } + mp = cache.iamGroupPolicyMap[name] + } + + // Compute net policy change effect and updated policy mapping + existingPolicySet := mp.policySet() + policiesToUpdate := set.CreateStringSet(policies...) + newPolicyMapping := mp + if isAttach { + // new policies to attach => inputPolicies - existing (set difference) + policiesToUpdate = policiesToUpdate.Difference(existingPolicySet) + // validate that new policies to add are defined. + for _, p := range policiesToUpdate.ToSlice() { + if _, found := cache.iamPolicyDocsMap[p]; !found { + return updatedAt, nil, errNoSuchPolicy + } + } + newPolicyMapping.Policies = strings.Join(existingPolicySet.Union(policiesToUpdate).ToSlice(), ",") + } else { + // policies to detach => inputPolicies ∩ existing (intersection) + policiesToUpdate = policiesToUpdate.Intersection(existingPolicySet) + newPolicyMapping.Policies = strings.Join(existingPolicySet.Difference(policiesToUpdate).ToSlice(), ",") + } + newPolicyMapping.UpdatedAt = UTCNow() + + // We return an error if the requested policy update will have no effect. + if policiesToUpdate.IsEmpty() { + return updatedAt, nil, errNoPolicyToAttachOrDetach + } + + addedOrRemoved = policiesToUpdate.ToSlice() + + if err := store.saveMappedPolicy(ctx, name, userType, isGroup, newPolicyMapping); err != nil { + return updatedAt, addedOrRemoved, err + } + if !isGroup { + cache.iamUserPolicyMap[name] = newPolicyMapping + } else { + cache.iamGroupPolicyMap[name] = newPolicyMapping + } + cache.updatedAt = UTCNow() + return cache.updatedAt, addedOrRemoved, nil +} + // PolicyDBSet - update the policy mapping for the given user or group in // storage and in cache. We do not check for the existence of the user here // since users can be virtual, such as for: diff --git a/cmd/iam.go b/cmd/iam.go index 7da93bee4..413b308de 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1497,6 +1497,58 @@ func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, userTyp return updatedAt, nil } +// PolicyDBUpdateLDAP - adds or removes policies from a user or a group verified +// to be in the LDAP directory. +func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool, + r madmin.PolicyAssociationReq, +) (updatedAt time.Time, addedOrRemoved []string, err error) { + if !sys.Initialized() { + return updatedAt, nil, errServerNotInitialized + } + + var dn string + var isGroup bool + if r.User != "" { + dn, err = globalLDAPConfig.DoesUsernameExist(r.User) + if err != nil { + logger.LogIf(ctx, err) + return updatedAt, nil, err + } + if dn == "" { + return updatedAt, nil, errNoSuchUser + } + isGroup = false + } else { + if exists, err := globalLDAPConfig.DoesGroupDNExist(r.Group); err != nil { + logger.LogIf(ctx, err) + return updatedAt, nil, err + } else if !exists { + return updatedAt, nil, errNoSuchGroup + } + dn = r.Group + isGroup = true + } + + userType := stsUser + updatedAt, addedOrRemoved, err = sys.store.PolicyDBUpdate(ctx, dn, isGroup, + userType, r.Policies, isAttach) + if err != nil { + return updatedAt, nil, err + } + + // Notify all other MinIO peers to reload policy + if !sys.HasWatcher() { + for _, nerr := range globalNotificationSys.LoadPolicyMapping(dn, userType, isGroup) { + if nerr.Err != nil { + logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) + logger.LogIf(ctx, nerr.Err) + } + } + } + + return updatedAt, addedOrRemoved, nil +} + // PolicyDBGet - gets policy set on a user or group. If a list of groups is // given, policies associated with them are included as well. func (sys *IAMSys) PolicyDBGet(name string, isGroup bool, groups ...string) ([]string, error) { diff --git a/cmd/typed-errors.go b/cmd/typed-errors.go index f18ee3716..9494a7c78 100644 --- a/cmd/typed-errors.go +++ b/cmd/typed-errors.go @@ -80,6 +80,10 @@ var errNoSuchAccount = errors.New("Specified account does not exist") // error returned in IAM subsystem when groups doesn't exist. var errNoSuchGroup = errors.New("Specified group does not exist") +// error returned in IAM subsystem when a policy attach/detach request has no +// net effect, i.e. it is already applied. +var errNoPolicyToAttachOrDetach = errors.New("Specified policy update has no net effect") + // error returned in IAM subsystem when a non-empty group needs to be // deleted. var errGroupNotEmpty = errors.New("Specified group is not empty - cannot remove it") diff --git a/go.mod b/go.mod index 675273926..194dea54b 100644 --- a/go.mod +++ b/go.mod @@ -49,9 +49,9 @@ require ( github.com/minio/dperf v0.4.2 github.com/minio/highwayhash v1.0.2 github.com/minio/kes v0.22.0 - github.com/minio/madmin-go/v2 v2.0.0 + github.com/minio/madmin-go/v2 v2.0.1 github.com/minio/minio-go/v7 v7.0.44 - github.com/minio/pkg v1.5.5 + github.com/minio/pkg v1.5.6 github.com/minio/selfupdate v0.5.0 github.com/minio/sha256-simd v1.0.0 github.com/minio/simdjson-go v0.4.2 diff --git a/go.sum b/go.sum index 73217a7d8..4d02f6a5f 100644 --- a/go.sum +++ b/go.sum @@ -764,8 +764,8 @@ github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLT github.com/minio/kes v0.22.0 h1:3PGIgjHTC5fAjmqfUcLIcSxV5CaxPyjY3q8q28UF158= github.com/minio/kes v0.22.0/go.mod h1:q5T0uTFrr7l6GosXvF0ufCtUKkbmbSZW1Yhu4KgLKE8= github.com/minio/madmin-go v1.6.6/go.mod h1:ATvkBOLiP3av4D++2v1UEHC/QzsGtgXD5kYvvRYzdKs= -github.com/minio/madmin-go/v2 v2.0.0 h1:VR+zCIeoHPveppvMUhZ/vmok1UJp4pH38M4wkqpyT88= -github.com/minio/madmin-go/v2 v2.0.0/go.mod h1:5aFi/VLWBHC2DEFfGIlUmAeJhaF4ZAjuYpEWZFU14Zw= +github.com/minio/madmin-go/v2 v2.0.1 h1:WFfe12P18k9WSEFUZzUaBOQ78vjMBafM1YjgtXkkJoM= +github.com/minio/madmin-go/v2 v2.0.1/go.mod h1:5aFi/VLWBHC2DEFfGIlUmAeJhaF4ZAjuYpEWZFU14Zw= github.com/minio/mc v0.0.0-20221201184114-854b4f123f03 h1:/q0NA3KjhTL+q/R9xEye0lpJECJmwaZnuIBsBn4HP28= github.com/minio/mc v0.0.0-20221201184114-854b4f123f03/go.mod h1:+Jrdvdo6p83JtqUO38UUeTu4aspklp9cF9k6DqFkb0Q= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= @@ -774,8 +774,8 @@ github.com/minio/minio-go/v7 v7.0.41/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASM github.com/minio/minio-go/v7 v7.0.44 h1:9zUJ7iU7ax2P1jOvTp6nVrgzlZq3AZlFm0XfRFDKstM= github.com/minio/minio-go/v7 v7.0.44/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw= github.com/minio/pkg v1.5.4/go.mod h1:2MOaRFdmFKULD+uOLc3qHLGTQTuxCNPKNPfLBTxC8CA= -github.com/minio/pkg v1.5.5 h1:z53jAVkXpRD+Y4LBt9cN+EaPUbWD6iOWhZuC90JvGqk= -github.com/minio/pkg v1.5.5/go.mod h1:EiGlHS2xaooa2VMxhJsxxAZHDObHVUB3HwtuoEXOCVE= +github.com/minio/pkg v1.5.6 h1:4OUvRU1gDWilu/dohkJMVapylXN8q94kU5MgkOJ/x0I= +github.com/minio/pkg v1.5.6/go.mod h1:EiGlHS2xaooa2VMxhJsxxAZHDObHVUB3HwtuoEXOCVE= github.com/minio/selfupdate v0.5.0 h1:0UH1HlL49+2XByhovKl5FpYTjKfvrQ2sgL1zEXK6mfI= github.com/minio/selfupdate v0.5.0/go.mod h1:mcDkzMgq8PRcpCRJo/NlPY7U45O5dfYl2Y0Rg7IustY= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= diff --git a/internal/config/identity/ldap/ldap.go b/internal/config/identity/ldap/ldap.go index 36451afd3..8a8e3161a 100644 --- a/internal/config/identity/ldap/ldap.go +++ b/internal/config/identity/ldap/ldap.go @@ -18,6 +18,7 @@ package ldap import ( + "errors" "fmt" "strconv" "strings" @@ -56,6 +57,126 @@ func (l *Config) LookupUserDN(username string) (string, []string, error) { return bindDN, groups, nil } +// DoesUsernameExist checks if the given username exists in the LDAP directory. +// The given username could be just the short "login" username or the full DN. +// When the username is found, the full DN is returned, otherwise the returned +// string is empty. If the user is not found, err = nil, otherwise, err != nil. +func (l *Config) DoesUsernameExist(username string) (string, error) { + conn, err := l.LDAP.Connect() + if err != nil { + return "", err + } + defer conn.Close() + + // Bind to the lookup user account + if err = l.LDAP.LookupBind(conn); err != nil { + return "", err + } + + // Check if the passed in username is a valid DN. + parsedUsernameDN, err := ldap.ParseDN(username) + if err != nil { + // Since the passed in username was not a DN, we consider it as a login + // username and attempt to check it exists in the directory. + bindDN, err := l.LDAP.LookupUserDN(conn, username) + if err != nil { + if strings.Contains(err.Error(), "not found") { + return "", nil + } + return "", fmt.Errorf("Unable to find user DN: %w", err) + } + return bindDN, nil + } + + // Since the username is a valid DN, check that it is under a configured + // base DN in the LDAP directory. + var foundDistName []string + for _, baseDN := range l.LDAP.UserDNSearchBaseDistNames { + // BaseDN should not fail to parse. + baseDNParsed, _ := ldap.ParseDN(baseDN) + if baseDNParsed.AncestorOf(parsedUsernameDN) { + searchRequest := ldap.NewSearchRequest(username, ldap.ScopeBaseObject, ldap.NeverDerefAliases, + 0, 0, false, "(objectClass=*)", nil, nil) + searchResult, err := conn.Search(searchRequest) + if err != nil { + // Check if there is no matching result. + // Ref: https://ldap.com/ldap-result-code-reference/ + if ldap.IsErrorWithCode(err, 32) { + continue + } + return "", err + } + for _, entry := range searchResult.Entries { + foundDistName = append(foundDistName, entry.DN) + } + } + } + + if len(foundDistName) == 1 { + return foundDistName[0], nil + } else if len(foundDistName) > 1 { + // FIXME: This error would happen if the multiple base DNs are given and + // some base DNs are subtrees of other base DNs - we should validate + // and error out in such cases. + return "", fmt.Errorf("found multiple DNs for the given username") + } + return "", nil +} + +// DoesGroupDNExist checks if the given group DN exists in the LDAP directory. +func (l *Config) DoesGroupDNExist(groupDN string) (bool, error) { + if len(l.LDAP.GroupSearchBaseDistNames) == 0 { + return false, errors.New("no group search Base DNs given") + } + + gdn, err := ldap.ParseDN(groupDN) + if err != nil { + return false, fmt.Errorf("Given group DN could not be parsed: %s", err) + } + + conn, err := l.LDAP.Connect() + if err != nil { + return false, err + } + defer conn.Close() + + // Bind to the lookup user account + if err = l.LDAP.LookupBind(conn); err != nil { + return false, err + } + + var foundDistName []string + for _, baseDN := range l.LDAP.GroupSearchBaseDistNames { + // BaseDN should not fail to parse. + baseDNParsed, _ := ldap.ParseDN(baseDN) + if baseDNParsed.AncestorOf(gdn) { + searchRequest := ldap.NewSearchRequest(groupDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", nil, nil) + searchResult, err := conn.Search(searchRequest) + if err != nil { + // Check if there is no matching result. + // Ref: https://ldap.com/ldap-result-code-reference/ + if ldap.IsErrorWithCode(err, 32) { + continue + } + return false, err + } + for _, entry := range searchResult.Entries { + foundDistName = append(foundDistName, entry.DN) + } + } + } + if len(foundDistName) == 1 { + return true, nil + } else if len(foundDistName) > 1 { + // FIXME: This error would happen if the multiple base DNs are given and + // some base DNs are subtrees of other base DNs - we should validate + // and error out in such cases. + return false, fmt.Errorf("found multiple DNs for the given group DN") + } else { + return false, nil + } +} + // Bind - binds to ldap, searches LDAP and returns the distinguished name of the // user and the list of groups. func (l *Config) Bind(username, password string) (string, []string, error) {