Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
249 lines
8.8 KiB
Go
249 lines
8.8 KiB
Go
package sts
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
)
|
|
|
|
// ComputeParentUser returns a stable per-identity hash derived from the OIDC
|
|
// (sub, iss) tuple. Only the (sub, iss) pair is guaranteed stable across token
|
|
// refreshes per OpenID Connect Core 1.0 §5.7, so any per-user state (audit
|
|
// logs, quotas) must key off this value rather than the access-key or session
|
|
// id. The hash is base64-rawurl-encoded SHA-256 over "openid:<sub>:<iss>" so
|
|
// it stays filesystem-safe and bounded in length for storage in audit paths.
|
|
func ComputeParentUser(sub, iss string) string {
|
|
if sub == "" || iss == "" {
|
|
return ""
|
|
}
|
|
h := sha256.Sum256([]byte("openid:" + sub + ":" + iss))
|
|
return base64.RawURLEncoding.EncodeToString(h[:])
|
|
}
|
|
|
|
// identityClaimPriority is the order in which ResolveIdentityClaim looks for a
|
|
// human-readable, authoritative identity attribute in an STS request context.
|
|
// The context is populated at federation time from the validated OIDC token
|
|
// (see AssumeRoleWithWebIdentity), so every entry here is server-asserted and
|
|
// not client-supplied. preferred_username/email/name are conventional OIDC
|
|
// user claims; sub is the always-present stable subject identifier and the
|
|
// final fallback so a federated session never audits as fully anonymous.
|
|
var identityClaimPriority = []string{"preferred_username", "email", "name", "sub"}
|
|
|
|
// ResolveIdentityClaim returns the most human-readable authoritative identity
|
|
// claim available in ctx, or "" when none is present. ctx is the STS request
|
|
// context (sessionInfo.RequestContext) populated from the validated OIDC token
|
|
// at federation time. Non-string values are skipped so a structured claim
|
|
// never leaks into an audit-facing field. Whitespace-only values are treated
|
|
// as absent so a blank preferred claim does not mask a usable email or sub.
|
|
func ResolveIdentityClaim(ctx map[string]interface{}) string {
|
|
if len(ctx) == 0 {
|
|
return ""
|
|
}
|
|
for _, key := range identityClaimPriority {
|
|
if v, ok := ctx[key].(string); ok {
|
|
if trimmed := strings.TrimSpace(v); trimmed != "" {
|
|
return trimmed
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// defaultCredentialGenerator is a reusable instance for generating temporary credentials
|
|
// Reusing a single instance across all calls to ToSessionInfo() reduces allocation overhead
|
|
// since this method may be called frequently during signature verification
|
|
var defaultCredentialGenerator = NewCredentialGenerator()
|
|
|
|
// STSSessionClaims represents comprehensive session information embedded in JWT tokens
|
|
// This eliminates the need for separate session storage by embedding all session
|
|
// metadata directly in the token itself - enabling true stateless operation
|
|
type STSSessionClaims struct {
|
|
jwt.RegisteredClaims
|
|
|
|
// Session identification
|
|
SessionId string `json:"sid"` // session_id (abbreviated for smaller tokens)
|
|
SessionName string `json:"snam"` // session_name (abbreviated for smaller tokens)
|
|
TokenType string `json:"typ"` // token_type
|
|
|
|
// Role information
|
|
RoleArn string `json:"role"` // role_arn
|
|
AssumedRole string `json:"assumed"` // assumed_role_user
|
|
Principal string `json:"principal"` // principal_arn
|
|
|
|
// Authorization data
|
|
Policies []string `json:"pol,omitempty"` // policies (abbreviated)
|
|
// SessionPolicy contains inline session policy JSON (optional)
|
|
SessionPolicy string `json:"spol,omitempty"`
|
|
|
|
// Identity provider information
|
|
IdentityProvider string `json:"idp"` // identity_provider
|
|
ExternalUserId string `json:"ext_uid"` // external_user_id
|
|
ProviderIssuer string `json:"prov_iss"` // provider_issuer
|
|
|
|
// Request context (optional, for policy evaluation)
|
|
RequestContext map[string]interface{} `json:"req_ctx,omitempty"`
|
|
|
|
// Session metadata
|
|
AssumedAt time.Time `json:"assumed_at"` // when role was assumed
|
|
MaxDuration int64 `json:"max_dur,omitempty"` // maximum session duration in seconds
|
|
|
|
// ParentUser is a stable hash of (sub, iss) for tokens minted from an OIDC
|
|
// identity. It survives token rotation since only the (sub, iss) tuple is
|
|
// guaranteed stable per OpenID Connect Core 1.0. Empty for non-federated
|
|
// session types.
|
|
ParentUser string `json:"puid,omitempty"`
|
|
}
|
|
|
|
// NewSTSSessionClaims creates new STS session claims with all required information
|
|
func NewSTSSessionClaims(sessionId, issuer string, expiresAt time.Time) *STSSessionClaims {
|
|
now := time.Now()
|
|
return &STSSessionClaims{
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
Issuer: issuer,
|
|
Subject: sessionId,
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
|
NotBefore: jwt.NewNumericDate(now),
|
|
// jti = sessionId. The session id is already a unique random
|
|
// identifier, so reusing it as the JWT id avoids a second secret
|
|
// while still giving the revocation layer a stable lookup key.
|
|
ID: sessionId,
|
|
},
|
|
SessionId: sessionId,
|
|
TokenType: TokenTypeSession,
|
|
AssumedAt: now,
|
|
}
|
|
}
|
|
|
|
// ToSessionInfo converts JWT claims back to SessionInfo structure
|
|
// This enables seamless integration with existing code expecting SessionInfo
|
|
func (c *STSSessionClaims) ToSessionInfo() *SessionInfo {
|
|
var expiresAt time.Time
|
|
if c.ExpiresAt != nil {
|
|
expiresAt = c.ExpiresAt.Time
|
|
}
|
|
|
|
// Generate temporary credentials from the session ID
|
|
// This is deterministic based on the session ID, so the same credentials are regenerated
|
|
credentials, err := defaultCredentialGenerator.GenerateTemporaryCredentials(c.SessionId, expiresAt)
|
|
if err != nil {
|
|
// Log the error with context - credential generation failure is important for debugging
|
|
errMsg := fmt.Errorf("generate temporary credentials for session %s: %w", c.SessionId, err)
|
|
glog.Warningf("Failed to generate credentials for STS session: %v", errMsg)
|
|
// Return session info without credentials - validation will catch this as invalid
|
|
credentials = nil
|
|
}
|
|
|
|
return &SessionInfo{
|
|
SessionId: c.SessionId,
|
|
SessionName: c.SessionName,
|
|
RoleArn: c.RoleArn,
|
|
AssumedRoleUser: c.AssumedRole,
|
|
Principal: c.Principal,
|
|
Policies: c.Policies,
|
|
SessionPolicy: c.SessionPolicy,
|
|
ExpiresAt: expiresAt,
|
|
IdentityProvider: c.IdentityProvider,
|
|
ExternalUserId: c.ExternalUserId,
|
|
ProviderIssuer: c.ProviderIssuer,
|
|
RequestContext: c.RequestContext,
|
|
ParentUser: c.ParentUser,
|
|
// Provide the Subject (sub) from registered claims
|
|
Subject: c.Subject,
|
|
Credentials: credentials,
|
|
}
|
|
}
|
|
|
|
// IsValid checks if the session claims are valid (not expired, etc.)
|
|
func (c *STSSessionClaims) IsValid() bool {
|
|
now := time.Now()
|
|
|
|
// Check expiration
|
|
if c.ExpiresAt != nil && c.ExpiresAt.Before(now) {
|
|
return false
|
|
}
|
|
|
|
// Check not-before
|
|
if c.NotBefore != nil && c.NotBefore.After(now) {
|
|
return false
|
|
}
|
|
|
|
// Ensure required fields are present
|
|
if c.SessionId == "" || c.RoleArn == "" || c.Principal == "" {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetSessionId returns the session identifier
|
|
func (c *STSSessionClaims) GetSessionId() string {
|
|
return c.SessionId
|
|
}
|
|
|
|
// GetExpiresAt returns the expiration time
|
|
func (c *STSSessionClaims) GetExpiresAt() time.Time {
|
|
if c.ExpiresAt != nil {
|
|
return c.ExpiresAt.Time
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
// WithRoleInfo sets role-related information in the claims
|
|
func (c *STSSessionClaims) WithRoleInfo(roleArn, assumedRole, principal string) *STSSessionClaims {
|
|
c.RoleArn = roleArn
|
|
c.AssumedRole = assumedRole
|
|
c.Principal = principal
|
|
return c
|
|
}
|
|
|
|
// WithPolicies sets the policies associated with this session
|
|
func (c *STSSessionClaims) WithPolicies(policies []string) *STSSessionClaims {
|
|
c.Policies = policies
|
|
return c
|
|
}
|
|
|
|
// WithSessionPolicy sets the inline session policy JSON for this session
|
|
func (c *STSSessionClaims) WithSessionPolicy(policy string) *STSSessionClaims {
|
|
c.SessionPolicy = policy
|
|
return c
|
|
}
|
|
|
|
// WithIdentityProvider sets identity provider information
|
|
func (c *STSSessionClaims) WithIdentityProvider(providerName, externalUserId, providerIssuer string) *STSSessionClaims {
|
|
c.IdentityProvider = providerName
|
|
c.ExternalUserId = externalUserId
|
|
c.ProviderIssuer = providerIssuer
|
|
return c
|
|
}
|
|
|
|
// WithRequestContext sets request context for policy evaluation
|
|
func (c *STSSessionClaims) WithRequestContext(ctx map[string]interface{}) *STSSessionClaims {
|
|
c.RequestContext = ctx
|
|
return c
|
|
}
|
|
|
|
// WithMaxDuration sets the maximum session duration
|
|
func (c *STSSessionClaims) WithMaxDuration(duration time.Duration) *STSSessionClaims {
|
|
c.MaxDuration = int64(duration.Seconds())
|
|
return c
|
|
}
|
|
|
|
// WithSessionName sets the session name
|
|
func (c *STSSessionClaims) WithSessionName(sessionName string) *STSSessionClaims {
|
|
c.SessionName = sessionName
|
|
return c
|
|
}
|
|
|
|
// WithParentUser sets the stable per-identity hash for the session. See
|
|
// ComputeParentUser for the derivation rule.
|
|
func (c *STSSessionClaims) WithParentUser(parentUser string) *STSSessionClaims {
|
|
c.ParentUser = parentUser
|
|
return c
|
|
}
|