// Copyright 2026 Versity Software // This file is licensed under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package iamutil import ( "crypto/rand" "encoding/base64" "regexp" "github.com/versity/versitygw/debuglogger" "github.com/versity/versitygw/iamapi/iamerr" "github.com/versity/versitygw/internal/sigv4auth" ) const ( AccessKeyStatusActive = "Active" AccessKeyStatusInactive = "Inactive" accessKeyIDPrefix = "AKIA" accessKeyIDRandomLen = 17 minAccessKeyIDLen = 16 maxAccessKeyIDLen = 128 secretAccessKeyBytes = 30 sessionTokenBytes = 128 ) var accessKeyIDPattern = regexp.MustCompile(`^[\w]+$`) // GenerateAccessKeyID returns a new cryptographically random IAM access key // id in the AKIA… format. func GenerateAccessKeyID() (string, error) { id, err := generateAWSID(accessKeyIDPrefix, accessKeyIDRandomLen) if err != nil { debuglogger.Logf("failed to generate IAM access key id: %v", err) return "", err } return id, nil } // GenerateSecretAccessKey returns a new cryptographically random 40 character // secret access key. func GenerateSecretAccessKey() (string, error) { b := make([]byte, secretAccessKeyBytes) if _, err := rand.Read(b); err != nil { debuglogger.Logf("failed to generate IAM secret access key: %v", err) return "", err } return base64.StdEncoding.EncodeToString(b), nil } // GenerateTempAccessKeyID returns a new cryptographically random temporary // access key id in the ASIA… format, for credentials minted by // AssumeRoleWithWebIdentity. func GenerateTempAccessKeyID() (string, error) { id, err := generateAWSID(sigv4auth.TempAccessKeyIDPrefix, accessKeyIDRandomLen) if err != nil { debuglogger.Logf("failed to generate temporary IAM access key id: %v", err) return "", err } return id, nil } // GenerateSessionToken returns a new cryptographically random opaque // session token for temporary credentials. Unlike AWS's own STS, whose // session token self-encodes the session (so any STS host can validate it // without shared state), this gateway looks the token up in its own // session store, so an opaque random value is sufficient. func GenerateSessionToken() (string, error) { b := make([]byte, sessionTokenBytes) if _, err := rand.Read(b); err != nil { debuglogger.Logf("failed to generate IAM session token: %v", err) return "", err } return base64.RawURLEncoding.EncodeToString(b), nil } // IsTempAccessKeyID reports whether accessKeyID has the ASIA… prefix used // for temporary credentials minted by AssumeRoleWithWebIdentity, as opposed // to a long-term AKIA… access key. It delegates to sigv4auth so the S3 // gateway, which cannot import this package, shares one definition. func IsTempAccessKeyID(accessKeyID string) bool { return sigv4auth.IsTempAccessKeyID(accessKeyID) } // ValidateAccessKeyID checks that accessKeyID fits within the allowed length // range and character set. func ValidateAccessKeyID(accessKeyID string) error { if len(accessKeyID) < minAccessKeyIDLen { debuglogger.Logf("IAM access key id too short: value=%q", accessKeyID) return iamerr.AccessKeyIDTooShort(minAccessKeyIDLen) } if len(accessKeyID) > maxAccessKeyIDLen { debuglogger.Logf("IAM access key id too long: value=%q", accessKeyID) return iamerr.AccessKeyIDTooLong(maxAccessKeyIDLen) } if !accessKeyIDPattern.MatchString(accessKeyID) { debuglogger.Logf("invalid IAM access key id characters: value=%q", accessKeyID) return iamerr.GetAPIError(iamerr.ErrInvalidAccessKeyIDChars) } return nil } // ValidateAccessKeyStatus checks that status is either Active or Inactive. func ValidateAccessKeyStatus(status string) error { if status != AccessKeyStatusActive && status != AccessKeyStatusInactive { debuglogger.Logf("invalid IAM access key status: %q", status) return iamerr.InvalidAccessKeyStatus(status) } return nil }