ydb-platform / ydb

YDB is an open source Distributed SQL Database that combines high availability and scalability with strong consistency and ACID transactions
https://ydb.tech
Apache License 2.0
3.71k stars 507 forks source link

OAuth 2.0 Token Exchange credentials provider in SDKs #3542

Open UgnineSirdis opened 3 months ago

UgnineSirdis commented 3 months ago

RFC: https://www.rfc-editor.org/rfc/rfc8693

Aim: to make a credentials provider that uses a standard oauth 2.0 token exchange protocol suitable for many clouds/systems It must support:

I suggest the following. The example is in go, but we must provide equivalent interface for other SDKs (with equivalent names of classes)

type Oauth2TokenExchangeOptions struct {
    TokenEndpoint string

    // grant_type parameter
    // urn:ietf:params:oauth:grant-type:token-exchange by default
    GrantType string

    Resource string
    Audience []string
    Scope []string

    // requested_token_type parameter
    // urn:ietf:params:oauth:token-type:access_token by default
    RequestedTokenType string

    SubjectTokenSource *TokenSource

    ActorTokenSource *TokenSource
}

type Token struct {
    Token string

    // token type according to OAuth 2.0 token exchange protocol
    // https://www.rfc-editor.org/rfc/rfc8693#TokenTypeIdentifiers
    // for example urn:ietf:params:oauth:token-type:jwt
    TokenType string
}

type TokenSource interface {
    Token() (Token, error)
}

// Create new credentials that get token via OAuth 2.0 token exchange protocol
// https://www.rfc-editor.org/rfc/rfc8693
func NewOauth2TokenExchangeCredentials(opts Oauth2TokenExchangeOptions) (*credentials.Credentials, error)

type JWTTokenSourceOptions struct {
    SigningMethod jwt.SigningMethod
    KeyID string
    PrivateKey interface{} // symmetric key in case of symmetric algorithm

    // Claims
    Issuer string
    Subject string
    Audience []string
    ID string
    NotBefore int64
    TokenTTL time.Duration
}

// Creates new token source for OAuth 2.0 credentials provider which signs JWT token for exchange
func NewJWTTokenSource(opts JWTTokenSourceOptions) (*TokenSource, error)
UgnineSirdis commented 3 months ago

The example for particular Nebius credentials initialization:

func NewNebiusCredentials(keyID string, privateKeyContent []byte, serviceAccountId string) (*credentials.Credentials, error) {
    key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyContent)
    if err != nil {
        return err
    }

    jwtTokenSourceOpts := JWTTokenSourceOptions{
        SigningMethod: jwt.SigningMethodRS256,
        KeyID: keyID,
        PrivateKey: key,
        Issuer: serviceAccountId,
        Subject: serviceAccountId,
        Audience: []string{"token-service.iam.new.nebiuscloud.net"},
        TokenTTL: time.Hour,
    }

    jwtTokenSource, err := NewJWTTokenSource(jwtTokenSourceOpts)
    if err != nil {
        return err
    }

    oauth2TokenCredentialsOpts := Oauth2TokenExchangeOptions{
        TokenEndpoint: "https://auth.new.nebiuscloud.net/oauth2/token/exchange",
        Audience: []string{"token-service.iam.new.nebiuscloud.net"},
        SubjectTokenSource: jwtTokenSource,
    }

    return NewOauth2TokenExchangeCredentials(oauth2TokenCredentialsOpts)
}
UgnineSirdis commented 3 months ago

Merged implementation. Example of usage in go: https://github.com/ydb-platform/ydb-go-sdk/blob/master/examples/auth/oauth2_token_exchange_credentials/main.go

db, err := ydb.Open(ctx, dsn,
        ydb.WithOauth2TokenExchangeCredentials(
            credentials.WithTokenEndpoint(tokenEndpoint),
            credentials.WithAudience(audience),
            credentials.WithJWTSubjectToken(
                credentials.WithSigningMethod(jwt.SigningMethodRS256),
                credentials.WithKeyID(keyID),
                credentials.WithRSAPrivateKeyPEMFile(privateKeyFile),
                credentials.WithIssuer(issuer),
                credentials.WithSubject(subject),
                credentials.WithAudience(audience),
            ),
        ),
    )