Compare commits

...

7 Commits

Author SHA1 Message Date
Mayuresh Chaubal 64c99ec2ce
Merge 902adf1599 into 534f4a9fb1 2025-10-02 08:30:28 +08:00
yangw 534f4a9fb1
fix: timeN function return final closure not be called (#21615)
VulnCheck / Analysis (push) Has been cancelled Details
2025-09-30 23:06:01 -07:00
jiuker 902adf1599
Merge branch 'master' into feature-sts-san-uri 2025-06-12 10:23:06 +08:00
Mayuresh Chaubal 4ab0449a35 cleaning value of tlsSubKey 2025-06-02 18:02:46 +02:00
Mayuresh Chaubal a0f7fdce4d fixing code comments 2025-06-02 18:02:46 +02:00
Mayuresh Chaubal 5d16e2b4bc added support for podman desktop 2025-06-02 18:02:46 +02:00
Mayuresh Chaubal 5666a30fb0 adding behavior with flag MINIO_IDENTITY_TLS_SUBJECT_USE_SANURI 2025-06-02 18:02:46 +02:00
3 changed files with 105 additions and 21 deletions

View File

@ -106,16 +106,14 @@ func (p *scannerMetrics) log(s scannerMetric, paths ...string) func(custom map[s
// time n scanner actions. // time n scanner actions.
// Use for s < scannerMetricLastRealtime // Use for s < scannerMetricLastRealtime
func (p *scannerMetrics) timeN(s scannerMetric) func(n int) func() { func (p *scannerMetrics) timeN(s scannerMetric) func(n int) {
startTime := time.Now() startTime := time.Now()
return func(n int) func() { return func(n int) {
return func() { duration := time.Since(startTime)
duration := time.Since(startTime)
atomic.AddUint64(&p.operations[s], uint64(n)) atomic.AddUint64(&p.operations[s], uint64(n))
if s < scannerMetricLastRealtime { if s < scannerMetricLastRealtime {
p.latency[s].add(duration) p.latency[s].add(duration)
}
} }
} }
} }

View File

@ -783,6 +783,24 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
writeSuccessResponseXML(w, encodedSuccessResponse) writeSuccessResponseXML(w, encodedSuccessResponse)
} }
func extractPolicyName(sanURI string) (string, error) {
parsedURL, err := url.Parse(sanURI)
if err != nil {
return "", err
}
key := parsedURL.Host + strings.ReplaceAll(parsedURL.Path, "/", "+")
if len(key) > 128 {
return "", errors.New("Policy URL " + key + " is more than 128 characters long.")
}
return key, nil
}
// AssumeRoleWithCertificate implements user authentication with client certificates. // AssumeRoleWithCertificate implements user authentication with client certificates.
// It verifies the client-provided X.509 certificate, maps the certificate to an S3 policy // It verifies the client-provided X.509 certificate, maps the certificate to an S3 policy
// and returns temp. S3 credentials to the client. // and returns temp. S3 credentials to the client.
@ -893,15 +911,44 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
} }
} }
// We map the X.509 subject common name to the policy. So, a client var tlsSubKey string
// with the common name "foo" will be associated with the policy "foo".
// Other mapping functions - e.g. public-key hash based mapping - are if !globalIAMSys.STSTLSConfig.TLSSubjectUseSanURI {
// possible but not implemented. // We map the X.509 subject common name to the policy. So, a client
// // with the common name "foo" will be associated with the policy "foo".
// Group mapping is not possible with standard X.509 certificates. // Other mapping functions - e.g. public-key hash based mapping - are
if certificate.Subject.CommonName == "" { // possible but not implemented.
writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, errors.New("certificate subject CN cannot be empty")) //
return // Group mapping is not possible with standard X.509 certificates.
if certificate.Subject.CommonName == "" {
writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, errors.New("certificate subject CN cannot be empty"))
return
}
tlsSubKey = certificate.Subject.CommonName
} else {
// We map the X.509 san uri to the policy. So, a client
// with the san uri "http://myapp" will be associated with the policy "http://myapp".
// Other mapping functions - e.g. public-key hash based mapping - are
// possible but not implemented.
//
// Group mapping is not possible with standard X.509 certificates.
if len(certificate.URIs) == 0 {
writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, errors.New("SAN URI not present in the certificate"))
return
}
// Pick first SAN URI
// Extract Policy Name From SAN URI
// Set Policy Name as Subject Key
policyName, err := extractPolicyName(certificate.URIs[0].String())
if err != nil {
writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, errors.New("Unable to convert from SAN URI to Policy Name"))
return
}
tlsSubKey = policyName
} }
expiry, err := globalIAMSys.STSTLSConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds)) expiry, err := globalIAMSys.STSTLSConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds))
@ -919,13 +966,14 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
} }
// Associate any service accounts to the certificate CN // Associate any service accounts to the certificate CN
parentUser := "tls" + getKeySeparator() + certificate.Subject.CommonName parentUser := "tls" + getKeySeparator() + tlsSubKey
claims[expClaim] = UTCNow().Add(expiry).Unix() claims[expClaim] = UTCNow().Add(expiry).Unix()
claims[subClaim] = certificate.Subject.CommonName claims[subClaim] = tlsSubKey
claims[audClaim] = certificate.Subject.Organization claims[audClaim] = certificate.Subject.Organization
claims[issClaim] = certificate.Issuer.CommonName claims[issClaim] = certificate.Issuer.CommonName
claims[parentClaim] = parentUser claims[parentClaim] = parentUser
tokenRevokeType := r.Form.Get(stsRevokeTokenType) tokenRevokeType := r.Form.Get(stsRevokeTokenType)
if tokenRevokeType != "" { if tokenRevokeType != "" {
claims[tokenRevokeTypeClaim] = tokenRevokeType claims[tokenRevokeTypeClaim] = tokenRevokeType
@ -943,7 +991,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
} }
tmpCredentials.ParentUser = parentUser tmpCredentials.ParentUser = parentUser
policyName := certificate.Subject.CommonName policyName := tlsSubKey
updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName) updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName)
if err != nil { if err != nil {
writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err) writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)

View File

@ -41,6 +41,22 @@ const (
// clients to obtain temp. credentials with arbitrary policy // clients to obtain temp. credentials with arbitrary policy
// permissions - including admin permissions. // permissions - including admin permissions.
EnvIdentityTLSSkipVerify = "MINIO_IDENTITY_TLS_SKIP_VERIFY" EnvIdentityTLSSkipVerify = "MINIO_IDENTITY_TLS_SKIP_VERIFY"
// EnvIdentityTLSSubjectSanURI is an environmental variable that is used to select
// Subject for verified certificate identity in JWT Claim.
// This claim is sent to Authorization Engine.
// If set to true, First URI will be used as subject instead of CommonName
// The URI will be converted into suitable policy name by following operations
// 1. remove protocol name (or scheme name) from URI
// 2. Replace all Path separators (ie /) from the Path in URI, this results in CleanedPath
// 3. Join Host+CleanedPath
// 4. If the above string becomes greater than 128 characters in length, then
// a proper error is thrown
// As example, http://my.domain:10000/my/app/path will be converted to
// my.domain:10000+my+app+path
// Valid values for this field are true and false
// By default, it will be false. Thus Common Name will be used
EnvIdentityTLSSubjectSanURI = "MINIO_IDENTITY_TLS_SUBJECT_USE_SANURI"
) )
// Config contains the STS TLS configuration for generating temp. // Config contains the STS TLS configuration for generating temp.
@ -52,6 +68,11 @@ type Config struct {
// certificate verification. It should only be set for // certificate verification. It should only be set for
// debugging or testing purposes. // debugging or testing purposes.
InsecureSkipVerify bool `json:"skip_verify"` InsecureSkipVerify bool `json:"skip_verify"`
// TLSSubjectUseSANUri, if set to true, uses first SAN URI from
// the client certificate as subject. This is done instead of
// using Common Name.
TLSSubjectUseSanURI bool `json:"use_san_uri"`
} }
const ( const (
@ -99,11 +120,18 @@ func Lookup(kvs config.KVS) (Config, error) {
if err != nil { if err != nil {
return Config{}, err return Config{}, err
} }
cfg.TLSSubjectUseSanURI, err = config.ParseBool(env.Get(EnvIdentityTLSSubjectSanURI, kvs.Get(tlsSubjectUseSanURI)))
if err != nil {
return Config{}, err
}
return cfg, nil return cfg, nil
} }
const ( const (
skipVerify = "skip_verify" skipVerify = "skip_verify"
tlsSubjectUseSanURI = "tls_subject_use_san_uri"
) )
// DefaultKVS is the default K/V config system for // DefaultKVS is the default K/V config system for
@ -113,6 +141,10 @@ var DefaultKVS = config.KVS{
Key: skipVerify, Key: skipVerify,
Value: "off", Value: "off",
}, },
config.KV{
Key: tlsSubjectUseSanURI,
Value: "off",
},
} }
// Help is the help and description for the STS API K/V configuration. // Help is the help and description for the STS API K/V configuration.
@ -123,4 +155,10 @@ var Help = config.HelpKVS{
Optional: true, Optional: true,
Type: "on|off", Type: "on|off",
}, },
config.HelpKV{
Key: tlsSubjectUseSanURI,
Description: `use cleaned value of first san uri from client certificate instead common name. cleaning results in stripping scheme, replacing path separator with plus sign in uri path and joining it with host name (default: 'off')`,
Optional: true,
Type: "on|off",
},
} }