feat: service account token exchange
This commit is contained in:
parent
c828290ef1
commit
7a109a763d
7 changed files with 71 additions and 10 deletions
|
@ -3,11 +3,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/cli"
|
"github.com/caos/oidc/pkg/cli"
|
||||||
"github.com/caos/oidc/pkg/rp"
|
"github.com/caos/oidc/pkg/rp"
|
||||||
"github.com/google/go-github/v31/github"
|
"github.com/google/go-github/v31/github"
|
||||||
githubOAuth "golang.org/x/oauth2/github"
|
githubOAuth "golang.org/x/oauth2/github"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -37,7 +38,6 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("OAuth flow failed")
|
fmt.Println("OAuth flow failed")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
fmt.Println("OAuth flow success")
|
fmt.Println("OAuth flow success")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ func (s *AuthStorage) GetKeySet(_ context.Context) (*jose.JSONWebKeySet, error)
|
||||||
pubkey := s.key.Public()
|
pubkey := s.key.Public()
|
||||||
return &jose.JSONWebKeySet{
|
return &jose.JSONWebKeySet{
|
||||||
Keys: []jose.JSONWebKey{
|
Keys: []jose.JSONWebKey{
|
||||||
jose.JSONWebKey{Key: pubkey, Use: "sig", Algorithm: "RS256", KeyID: "1"},
|
{Key: pubkey, Use: "sig", Algorithm: "RS256", KeyID: "1"},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package oidc
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
@ -25,6 +26,7 @@ const (
|
||||||
PromptSelectAccount Prompt = "select_account"
|
PromptSelectAccount Prompt = "select_account"
|
||||||
|
|
||||||
GrantTypeCode GrantType = "authorization_code"
|
GrantTypeCode GrantType = "authorization_code"
|
||||||
|
GrantTypeBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||||
|
|
||||||
BearerToken = "Bearer"
|
BearerToken = "Bearer"
|
||||||
)
|
)
|
||||||
|
@ -100,6 +102,13 @@ type AccessTokenResponse struct {
|
||||||
IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
|
IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JWTTokenRequest struct {
|
||||||
|
Scopes Scopes `schema:"scope"`
|
||||||
|
Audience []string `schema:"aud"`
|
||||||
|
IssuedAt time.Time `schema:"iat"`
|
||||||
|
ExpiresAt time.Time `schema:"exp"`
|
||||||
|
}
|
||||||
|
|
||||||
type TokenExchangeRequest struct {
|
type TokenExchangeRequest struct {
|
||||||
subjectToken string `schema:"subject_token"`
|
subjectToken string `schema:"subject_token"`
|
||||||
subjectTokenType string `schema:"subject_token_type"`
|
subjectTokenType string `schema:"subject_token_type"`
|
||||||
|
|
|
@ -273,6 +273,11 @@ func (p *DefaultOP) Signer() Signer {
|
||||||
func (p *DefaultOP) Crypto() Crypto {
|
func (p *DefaultOP) Crypto() Crypto {
|
||||||
return p.crypto
|
return p.crypto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DefaultOP) Verifier() rp.Verifier {
|
||||||
|
return p.verifier
|
||||||
|
}
|
||||||
|
|
||||||
func (p *DefaultOP) HandleReady(w http.ResponseWriter, r *http.Request) {
|
func (p *DefaultOP) HandleReady(w http.ResponseWriter, r *http.Request) {
|
||||||
probes := []ProbesFn{
|
probes := []ProbesFn{
|
||||||
ReadySigner(p.Signer()),
|
ReadySigner(p.Signer()),
|
||||||
|
@ -299,9 +304,13 @@ func (p *DefaultOP) HandleExchange(w http.ResponseWriter, r *http.Request) {
|
||||||
RequestError(w, r, ErrInvalidRequest("grant_type missing"))
|
RequestError(w, r, ErrInvalidRequest("grant_type missing"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if reqType == string(oidc.GrantTypeCode) {
|
switch reqType {
|
||||||
|
case string(oidc.GrantTypeCode):
|
||||||
CodeExchange(w, r, p)
|
CodeExchange(w, r, p)
|
||||||
return
|
return
|
||||||
|
case string(oidc.GrantTypeBearer):
|
||||||
|
JWTExchange(w, r, p)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
TokenExchange(w, r, p)
|
TokenExchange(w, r, p)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@ package op
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
"github.com/caos/oidc/pkg/rp"
|
||||||
"github.com/caos/oidc/pkg/utils"
|
"github.com/caos/oidc/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,6 +22,11 @@ type Exchanger interface {
|
||||||
AuthMethodPostSupported() bool
|
AuthMethodPostSupported() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VerifyExchanger interface {
|
||||||
|
Exchanger
|
||||||
|
Verifier() rp.Verifier
|
||||||
|
}
|
||||||
|
|
||||||
func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
||||||
tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder())
|
tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -116,6 +123,33 @@ func AuthorizeCodeChallenge(ctx context.Context, tokenReq *oidc.AccessTokenReque
|
||||||
return authReq, nil
|
return authReq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func JWTExchange(w http.ResponseWriter, r *http.Request, exchanger VerifyExchanger) {
|
||||||
|
assertion, err := ParseJWTTokenRequest(r, exchanger.Decoder())
|
||||||
|
if err != nil {
|
||||||
|
RequestError(w, r, err)
|
||||||
|
}
|
||||||
|
claims, err := exchanger.Verifier().Verify(r.Context(), "", assertion)
|
||||||
|
fmt.Println(claims, err)
|
||||||
|
|
||||||
|
_ = assertion
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseJWTTokenRequest(r *http.Request, decoder *schema.Decoder) (string, error) {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrInvalidRequest("error parsing form")
|
||||||
|
}
|
||||||
|
tokenReq := new(struct {
|
||||||
|
Token string `schema:"assertion"`
|
||||||
|
})
|
||||||
|
err = decoder.Decode(tokenReq, r.Form)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrInvalidRequest("error decoding form")
|
||||||
|
}
|
||||||
|
//TODO: validations
|
||||||
|
return tokenReq.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
||||||
tokenRequest, err := ParseTokenExchangeRequest(w, r)
|
tokenRequest, err := ParseTokenExchangeRequest(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -148,14 +148,25 @@ func DefaultACRVerifier(possibleValues []string) ACRVerifier {
|
||||||
//and https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation
|
//and https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation
|
||||||
func (v *DefaultVerifier) Verify(ctx context.Context, accessToken, idTokenString string) (*oidc.IDTokenClaims, error) {
|
func (v *DefaultVerifier) Verify(ctx context.Context, accessToken, idTokenString string) (*oidc.IDTokenClaims, error) {
|
||||||
v.config.now = time.Now().UTC()
|
v.config.now = time.Now().UTC()
|
||||||
idToken, err := v.VerifyIDToken(ctx, idTokenString)
|
// idToken, err := v.VerifyIDToken(ctx, idTokenString)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// if err := v.verifyAccessToken(accessToken, idToken.AccessTokenHash, idToken.Signature); err != nil { //TODO: sig from token
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return idToken, nil
|
||||||
|
|
||||||
|
// TODO: verifiy
|
||||||
|
decrypted, err := v.decryptToken(idTokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := v.verifyAccessToken(accessToken, idToken.AccessTokenHash, idToken.Signature); err != nil { //TODO: sig from token
|
claims, _, err := v.parseToken(decrypted)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return idToken, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DefaultVerifier) now() time.Time {
|
func (v *DefaultVerifier) now() time.Time {
|
||||||
|
|
|
@ -19,7 +19,6 @@ func EncryptAES(data string, key string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func EncryptBytesAES(plainText []byte, key string) ([]byte, error) {
|
func EncryptBytesAES(plainText []byte, key string) ([]byte, error) {
|
||||||
|
|
||||||
block, err := aes.NewCipher([]byte(key))
|
block, err := aes.NewCipher([]byte(key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -50,7 +49,6 @@ func DecryptAES(data string, key string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecryptBytesAES(cipherText []byte, key string) ([]byte, error) {
|
func DecryptBytesAES(cipherText []byte, key string) ([]byte, error) {
|
||||||
|
|
||||||
block, err := aes.NewCipher([]byte(key))
|
block, err := aes.NewCipher([]byte(key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue