Skip to content

Commit

Permalink
Merge pull request #1194 from UgnineSirdis/oauth2-token-exchange
Browse files Browse the repository at this point in the history
Implement OAuth 2.0 Token Exchange credentials provider in Go SDK
  • Loading branch information
asmyasnikov authored Apr 12, 2024
2 parents d38f4ec + b7a9b51 commit c7d27aa
Show file tree
Hide file tree
Showing 10 changed files with 1,599 additions and 4 deletions.
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
* Supported OAuth 2.0 Token Exchange credentials provider

## v3.64.0
* Supported `table.Session.RenameTables` method
* Fixed out of range panic if next query result set part is empty
* Updated the indirect dependencies `golang.org/x/net` to `v0.17.0` and `golang.org/x/sys` to `v0.13.0` due to vulnerability issue
* Fixed out of range panic if next query result set part is empty
* Updated the indirect dependencies `golang.org/x/net` to `v0.17.0` and `golang.org/x/sys` to `v0.13.0` due to vulnerability issue

## v3.63.0
* Added versioning policy
Expand All @@ -14,7 +16,7 @@
* Added `go` with anonymous function case in `gstack`

## v3.61.2
* Changed default transaction control to `NoTx` for execute query through query service client
* Changed default transaction control to `NoTx` for execute query through query service client

## v3.61.1
* Renamed `db.Coordination().CreateSession()` to `db.Coordination().Session()` for compatibility with protos
Expand Down Expand Up @@ -97,7 +99,7 @@
* Fixed sometime panic on topic writer closing
* Added experimental query parameters builder `ydb.ParamsBuilder()`
* Changed types of `table/table.{QueryParameters,ParameterOption}` to aliases on `internal/params.{Parameters,NamedValue}`
* Fixed bug with optional decimal serialization
* Fixed bug with optional decimal serialization

## v3.56.2
* Fixed return private error for commit to stopped partition in topic reader.
Expand Down
18 changes: 18 additions & 0 deletions credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,21 @@ func NewStaticCredentials(
) *credentials.Static {
return credentials.NewStaticCredentials(user, password, authEndpoint, opts...)
}

// NewOauth2TokenExchangeCredentials makes OAuth 2.0 token exchange protocol credentials object
// https://www.rfc-editor.org/rfc/rfc8693
func NewOauth2TokenExchangeCredentials(
opts ...credentials.Oauth2TokenExchangeCredentialsOption,
) (Credentials, error) {
return credentials.NewOauth2TokenExchangeCredentials(opts...)
}

// NewJWTTokenSource makes JWT token source for OAuth 2.0 token exchange credentials
func NewJWTTokenSource(opts ...credentials.JWTTokenSourceOption) (credentials.TokenSource, error) {
return credentials.NewJWTTokenSource(opts...)
}

// NewFixedTokenSource makes fixed token source for OAuth 2.0 token exchange credentials
func NewFixedTokenSource(token, tokenType string) credentials.TokenSource {
return credentials.NewFixedTokenSource(token, tokenType)
}
124 changes: 124 additions & 0 deletions credentials/options.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package credentials

import (
"time"

"github.com/golang-jwt/jwt/v4"
"google.golang.org/grpc"

"github.com/ydb-platform/ydb-go-sdk/v3/internal/credentials"
)

type Oauth2TokenExchangeCredentialsOption = credentials.Oauth2TokenExchangeCredentialsOption

type TokenSource = credentials.TokenSource

type Token = credentials.Token

// WithSourceInfo option append to credentials object the source info for reporting source info details on error case
func WithSourceInfo(sourceInfo string) credentials.SourceInfoOption {
return credentials.WithSourceInfo(sourceInfo)
Expand All @@ -15,3 +24,118 @@ func WithSourceInfo(sourceInfo string) credentials.SourceInfoOption {
func WithGrpcDialOptions(opts ...grpc.DialOption) credentials.StaticCredentialsOption {
return credentials.WithGrpcDialOptions(opts...)
}

// TokenEndpoint
func WithTokenEndpoint(endpoint string) Oauth2TokenExchangeCredentialsOption {
return credentials.WithTokenEndpoint(endpoint)
}

// GrantType
func WithGrantType(grantType string) Oauth2TokenExchangeCredentialsOption {
return credentials.WithGrantType(grantType)
}

// Resource
func WithResource(resource string) Oauth2TokenExchangeCredentialsOption {
return credentials.WithResource(resource)
}

// RequestedTokenType
func WithRequestedTokenType(requestedTokenType string) Oauth2TokenExchangeCredentialsOption {
return credentials.WithRequestedTokenType(requestedTokenType)
}

// Scope
func WithScope(scope ...string) Oauth2TokenExchangeCredentialsOption {
return credentials.WithScope(scope...)
}

// RequestTimeout
func WithRequestTimeout(timeout time.Duration) Oauth2TokenExchangeCredentialsOption {
return credentials.WithRequestTimeout(timeout)
}

// SubjectTokenSource
func WithSubjectToken(subjectToken credentials.TokenSource) Oauth2TokenExchangeCredentialsOption {
return credentials.WithSubjectToken(subjectToken)
}

// SubjectTokenSource
func WithFixedSubjectToken(token, tokenType string) Oauth2TokenExchangeCredentialsOption {
return credentials.WithFixedSubjectToken(token, tokenType)
}

// SubjectTokenSource
func WithJWTSubjectToken(opts ...credentials.JWTTokenSourceOption) Oauth2TokenExchangeCredentialsOption {
return credentials.WithJWTSubjectToken(opts...)
}

// ActorTokenSource
func WithActorToken(actorToken credentials.TokenSource) Oauth2TokenExchangeCredentialsOption {
return credentials.WithActorToken(actorToken)
}

// ActorTokenSource
func WithFixedActorToken(token, tokenType string) Oauth2TokenExchangeCredentialsOption {
return credentials.WithFixedActorToken(token, tokenType)
}

// ActorTokenSource
func WithJWTActorToken(opts ...credentials.JWTTokenSourceOption) Oauth2TokenExchangeCredentialsOption {
return credentials.WithJWTActorToken(opts...)
}

// Audience
type oauthCredentialsAndJWTCredentialsOption interface {
credentials.Oauth2TokenExchangeCredentialsOption
credentials.JWTTokenSourceOption
}

func WithAudience(audience ...string) oauthCredentialsAndJWTCredentialsOption {
return credentials.WithAudience(audience...)
}

// Issuer
func WithIssuer(issuer string) credentials.JWTTokenSourceOption {
return credentials.WithIssuer(issuer)
}

// Subject
func WithSubject(subject string) credentials.JWTTokenSourceOption {
return credentials.WithSubject(subject)
}

// ID
func WithID(id string) credentials.JWTTokenSourceOption {
return credentials.WithID(id)
}

// TokenTTL
func WithTokenTTL(ttl time.Duration) credentials.JWTTokenSourceOption {
return credentials.WithTokenTTL(ttl)
}

// SigningMethod
func WithSigningMethod(method jwt.SigningMethod) credentials.JWTTokenSourceOption {
return credentials.WithSigningMethod(method)
}

// KeyID
func WithKeyID(id string) credentials.JWTTokenSourceOption {
return credentials.WithKeyID(id)
}

// PrivateKey
func WithPrivateKey(key interface{}) credentials.JWTTokenSourceOption {
return credentials.WithPrivateKey(key)
}

// PrivateKey
func WithRSAPrivateKeyPEMContent(key []byte) credentials.JWTTokenSourceOption {
return credentials.WithRSAPrivateKeyPEMContent(key)
}

// PrivateKey
func WithRSAPrivateKeyPEMFile(path string) credentials.JWTTokenSourceOption {
return credentials.WithRSAPrivateKeyPEMFile(path)
}
1 change: 1 addition & 0 deletions examples/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Auth examples helps to understand YDB authentication:
* `access_token_credentials` - example of use access token credentials
* `anonymous_credentials` - example of use anonymous credentials
* `metadata_credentials` - example of use metadata credentials
* `oauth2_token_exchange_credentials` - example of use oauth 2.0 token exchange credentials
* `service_account_credentials` - example of use service account key file credentials
* `static_credentials` - example of use static credentials
* `environ` - example of use environment variables to configure YDB authenticate
Expand Down
8 changes: 8 additions & 0 deletions examples/auth/oauth2_token_exchange_credentials/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Authenticate with oauth 2.0 token exchange credentials

`oauth2_token_exchange_credentials` example provides code snippet for authentication to YDB with oauth 2.0 token exchange credentials

## Runing code snippet
```bash
oauth2_token_exchange_credentials -ydb="grpcs://endpoint/?database=database" -token-endpoint="https://exchange.token.endpoint/oauth2/token/exchange" -key-id="123" -private-key-file="path/to/key/file" -audience="test-aud" -issuer="test-issuer" -subject="test-subject"
```
107 changes: 107 additions & 0 deletions examples/auth/oauth2_token_exchange_credentials/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package main

import (
"context"
"flag"
"fmt"
"os"

"github.com/golang-jwt/jwt/v4"
ydb "github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/credentials"
)

var (
dsn string
tokenEndpoint string
keyID string
privateKeyFile string
audience string
issuer string
subject string
)

func init() { //nolint:gochecknoinits
required := []string{"ydb", "private-key-file", "key-id", "token-endpoint"}
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
flagSet.Usage = func() {
out := flagSet.Output()
_, _ = fmt.Fprintf(out, "Usage:\n%s [options]\n", os.Args[0])
_, _ = fmt.Fprintf(out, "\nOptions:\n")
flagSet.PrintDefaults()
}
flagSet.StringVar(&dsn,
"ydb", "",
"YDB connection string",
)
flagSet.StringVar(&tokenEndpoint,
"token-endpoint", "",
"oauth 2.0 token exchange endpoint",
)
flagSet.StringVar(&keyID,
"key-id", "",
"key id for jwt token",
)
flagSet.StringVar(&privateKeyFile,
"private-key-file", "",
"RSA private key file for jwt token in pem format",
)
flagSet.StringVar(&audience,
"audience", "",
"audience",
)
flagSet.StringVar(&issuer,
"issuer", "",
"jwt token issuer",
)
flagSet.StringVar(&subject,
"subject", "",
"jwt token subject",
)
if err := flagSet.Parse(os.Args[1:]); err != nil {
flagSet.Usage()
os.Exit(1)
}
flagSet.Visit(func(f *flag.Flag) {
for i, arg := range required {
if arg == f.Name {
required = append(required[:i], required[i+1:]...)
}
}
})
if len(required) > 0 {
fmt.Printf("\nSome required options not defined: %v\n\n", required)
flagSet.Usage()
os.Exit(1)
}
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
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),
),
),
)
if err != nil {
panic(err)
}
defer func() { _ = db.Close(ctx) }()

whoAmI, err := db.Discovery().WhoAmI(ctx)
if err != nil {
panic(err)
}

fmt.Println(whoAmI.String())
}
Loading

0 comments on commit c7d27aa

Please sign in to comment.