Skip to content

Commit

Permalink
feat(sdk): insecure plaintext and skip verify conn (#670)
Browse files Browse the repository at this point in the history
resolves #631

_breaking change in the SDK_

```
// WithInsecurePlaintextConn returns an Option that sets up HTTP connection sent in the clear.
func WithInsecurePlaintextConn() Option {

// WithInsecureSkipVerifyConn returns an Option that sets up HTTPS connection without verification.
func WithInsecureSkipVerifyConn() Option {
```

- Some log statement changes
  • Loading branch information
pflynn-virtru committed May 1, 2024
1 parent d2fda0c commit 5c94d02
Show file tree
Hide file tree
Showing 21 changed files with 158 additions and 128 deletions.
2 changes: 1 addition & 1 deletion examples/cmd/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func init() {
}

func attributesExample(examplesConfig *ExampleConfig) error {
s, err := sdk.New(examplesConfig.PlatformEndpoint, sdk.WithInsecureConn())
s, err := sdk.New(examplesConfig.PlatformEndpoint, sdk.WithInsecurePlaintextConn())
if err != nil {
slog.Error("could not connect", slog.String("error", err.Error()))
return err
Expand Down
2 changes: 1 addition & 1 deletion examples/cmd/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var AuthorizationExampleCmd = &cobra.Command{
}

func authorizationExamples(examplesConfig *ExampleConfig) error {
s, err := sdk.New(examplesConfig.PlatformEndpoint, sdk.WithInsecureConn())
s, err := sdk.New(examplesConfig.PlatformEndpoint, sdk.WithInsecurePlaintextConn())
if err != nil {
slog.Error("could not connect", slog.String("error", err.Error()))
return err
Expand Down
2 changes: 1 addition & 1 deletion examples/cmd/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func decrypt(cmd *cobra.Command, args []string) error {

// Create new client
client, err := sdk.New(cmd.Context().Value(RootConfigKey).(*ExampleConfig).PlatformEndpoint,
sdk.WithInsecureConn(),
sdk.WithInsecurePlaintextConn(),
sdk.WithClientCredentials("opentdf-sdk", "secret", nil),
sdk.WithTokenEndpoint("http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token"),
)
Expand Down
3 changes: 1 addition & 2 deletions examples/cmd/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ func encrypt(cmd *cobra.Command, args []string) error {
strReader := strings.NewReader(plainText)

// Create new offline client

client, err := sdk.New(cmd.Context().Value(RootConfigKey).(*ExampleConfig).PlatformEndpoint,
sdk.WithInsecureConn(),
sdk.WithInsecurePlaintextConn(),
sdk.WithClientCredentials("opentdf-sdk", "secret", nil),
sdk.WithTokenEndpoint("http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token"),
)
Expand Down
11 changes: 8 additions & 3 deletions sdk/auth/access_token_source.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package auth

import "github.com/lestrrat-go/jwx/v2/jwk"
import (
"context"
"net/http"

"github.com/lestrrat-go/jwx/v2/jwk"
)

type AccessToken string

type AccessTokenSource interface {
AccessToken() (AccessToken, error)
// probably better to use `crypto.AsymDecryption` here than roll our own since this should be
AccessToken(ctx context.Context, client *http.Client) (AccessToken, error)
// MakeToken probably better to use `crypto.AsymDecryption` here than roll our own since this should be
// more closely linked to what happens in KAS in terms of crypto params
MakeToken(func(jwk.Key) ([]byte, error)) ([]byte, error)
}
33 changes: 24 additions & 9 deletions sdk/auth/token_adding_interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"fmt"
"log/slog"
Expand All @@ -18,34 +19,48 @@ import (
)

const (
JTILength = 14
JWTExpirationMinutes = 10
JTILength = 14
)

func NewTokenAddingInterceptor(t AccessTokenSource) TokenAddingInterceptor {
return TokenAddingInterceptor{tokenSource: t}
func NewTokenAddingInterceptor(t AccessTokenSource, c *tls.Config) TokenAddingInterceptor {
return TokenAddingInterceptor{
tokenSource: t,
tlsConfig: c,
}
}

type TokenAddingInterceptor struct {
tokenSource AccessTokenSource
tlsConfig *tls.Config
}

func (i TokenAddingInterceptor) AddCredentials(ctx context.Context,
method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
func (i TokenAddingInterceptor) AddCredentials(
ctx context.Context,
method string,
req, reply any,
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
newMetadata := make([]string, 0)
accessToken, err := i.tokenSource.AccessToken()
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: i.tlsConfig,
},
}
accessToken, err := i.tokenSource.AccessToken(ctx, client)
if err == nil {
newMetadata = append(newMetadata, "Authorization", fmt.Sprintf("DPoP %s", accessToken))
} else {
slog.Error("error getting access token: %w. request will be unauthenticated", err)
slog.ErrorContext(ctx, "error getting access token. request will be unauthenticated", "error", err)
return invoker(ctx, method, req, reply, cc, opts...)
}

dpopTok, err := i.GetDPoPToken(method, http.MethodPost, string(accessToken))
if err == nil {
newMetadata = append(newMetadata, "DPoP", dpopTok)
} else {
slog.Error("error adding dpop token to outgoing request. Request will not have DPoP token", err)
slog.ErrorContext(ctx, "error adding dpop token to outgoing request. Request will not have DPoP token", err)
}

newCtx := metadata.AppendToOutgoingContext(ctx, newMetadata...)
Expand Down
21 changes: 18 additions & 3 deletions sdk/auth/token_adding_interceptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"errors"
"net"
"net/http"
"slices"
"testing"
"time"

"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/opentdf/platform/protocol/go/kas"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
Expand Down Expand Up @@ -47,7 +50,9 @@ func TestAddingTokensToOutgoingRequest(t *testing.T) {
accessToken: "thisisafakeaccesstoken",
}
server := FakeAccessServiceServer{}
oo := NewTokenAddingInterceptor(&ts)
oo := NewTokenAddingInterceptor(&ts, &tls.Config{
MinVersion: tls.VersionTLS12,
})

client, stop := runServer(context.Background(), &server, oo)
defer stop()
Expand Down Expand Up @@ -117,7 +122,9 @@ func TestAddingTokensToOutgoingRequest(t *testing.T) {
func Test_InvalidCredentials_StillSendMessage(t *testing.T) {
ts := FakeTokenSource{key: nil}
server := FakeAccessServiceServer{}
oo := NewTokenAddingInterceptor(&ts)
oo := NewTokenAddingInterceptor(&ts, &tls.Config{
MinVersion: tls.VersionTLS12,
})

client, stop := runServer(context.Background(), &server, oo)
defer stop()
Expand Down Expand Up @@ -163,7 +170,7 @@ type FakeTokenSource struct {
accessToken string
}

func (fts *FakeTokenSource) AccessToken() (AccessToken, error) {
func (fts *FakeTokenSource) AccessToken(context.Context, *http.Client) (AccessToken, error) {
return AccessToken(fts.accessToken), nil
}
func (fts *FakeTokenSource) MakeToken(f func(jwk.Key) ([]byte, error)) ([]byte, error) {
Expand All @@ -172,6 +179,14 @@ func (fts *FakeTokenSource) MakeToken(f func(jwk.Key) ([]byte, error)) ([]byte,
}
return f(fts.key)
}
func (fts *FakeTokenSource) Token() (*oauth2.Token, error) {
return &oauth2.Token{
AccessToken: fts.accessToken,
TokenType: "",
RefreshToken: "",
Expiry: time.Time{},
}, nil
}
func runServer(ctx context.Context, //nolint:ireturn // this is pretty concrete
f *FakeAccessServiceServer, oo TokenAddingInterceptor) (kas.AccessServiceClient, func()) {
buffer := 1024 * 1024
Expand Down
65 changes: 11 additions & 54 deletions sdk/auth_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type AuthConfig struct {
dpopPublicKeyPEM string
dpopPrivateKeyPEM string
accessToken string
client *http.Client
}

type RequestBody struct {
Expand All @@ -34,7 +35,7 @@ type rewrapJWTClaims struct {
}

// NewAuthConfig Create a new instance of authConfig
func NewAuthConfig() (*AuthConfig, error) {
func NewAuthConfig(client *http.Client) (*AuthConfig, error) {
rsaKeyPair, err := ocrypto.NewRSAKeyPair(tdf3KeySize)
if err != nil {
return nil, fmt.Errorf("ocrypto.NewRSAKeyPair failed: %w", err)
Expand All @@ -50,11 +51,15 @@ func NewAuthConfig() (*AuthConfig, error) {
return nil, fmt.Errorf("ocrypto.PrivateKeyInPemFormat failed: %w", err)
}

return &AuthConfig{dpopPublicKeyPEM: publicKey, dpopPrivateKeyPEM: privateKey}, nil
return &AuthConfig{
dpopPublicKeyPEM: publicKey,
dpopPrivateKeyPEM: privateKey,
client: client,
}, nil
}

func NewOIDCAuthConfig(ctx context.Context, host, realm, clientID, clientSecret, subjectToken string) (*AuthConfig, error) {
authConfig, err := NewAuthConfig()
func NewOIDCAuthConfig(ctx context.Context, client *http.Client, host, realm, clientID, clientSecret, subjectToken string) (*AuthConfig, error) {
authConfig, err := NewAuthConfig(client)
if err != nil {
return nil, err
}
Expand All @@ -80,8 +85,7 @@ func (a *AuthConfig) fetchOIDCAccessToken(ctx context.Context, host, realm, clie
certB64 := ocrypto.Base64Encode([]byte(a.dpopPublicKeyPEM))
req.Header.Set("X-VirtruPubKey", string(certB64))

client := &http.Client{}
resp, err := client.Do(req)
resp, err := a.client.Do(req)
if err != nil {
return "", fmt.Errorf("error making request to IdP for token exchange: %w", err)
}
Expand Down Expand Up @@ -151,9 +155,7 @@ func (a *AuthConfig) makeKASRequest(kasPath string, body *RequestBody) (*http.Re
kAcceptKey: {kContentTypeJSONValue},
}

client := &http.Client{}

response, err := client.Do(request)
response, err := a.client.Do(request)
if err != nil {
slog.Error("failed http request")
return nil, fmt.Errorf("http request failed: %w", err)
Expand Down Expand Up @@ -230,48 +232,3 @@ func getWrappedKey(rewrapResponseBody []byte, clientPrivateKey string) ([]byte,

return key, nil
}

func (*AuthConfig) getPublicKey(kasInfo KASInfo) (string, error) {
kasPubKeyURL, err := url.JoinPath(kasInfo.URL, kasPublicKeyPath)
if err != nil {
return "", fmt.Errorf("url.Parse failed: %w", err)
}

request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, kasPubKeyURL, nil)
if err != nil {
return "", fmt.Errorf("http.NewRequestWithContext failed: %w", err)
}

// add required headers
request.Header = http.Header{
kAcceptKey: {kContentTypeJSONValue},
}

client := &http.Client{}

response, err := client.Do(request)
defer func() {
if response == nil {
return
}
err := response.Body.Close()
if err != nil {
slog.Error("Fail to close HTTP response")
}
}()
if err != nil {
slog.Error("failed http request")
return "", fmt.Errorf("client.Do error: %w", err)
}
if response.StatusCode != kHTTPOk {
return "", fmt.Errorf("client.Do failed: %w", err)
}

var jsonResponse interface{}
err = json.NewDecoder(response.Body).Decode(&jsonResponse)
if err != nil {
return "", fmt.Errorf("json.NewDecoder.Decode failed: %w", err)
}

return fmt.Sprintf("%s", jsonResponse), nil
}
3 changes: 2 additions & 1 deletion sdk/auth_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func TestNewOIDCAuthConfig(t *testing.T) {
u, _ := url.Parse(s.URL)
host, port, _ := net.SplitHostPort(u.Host)

authConfig, err := NewOIDCAuthConfig(context.TODO(), "http://"+host+":"+port, realm, clientID, clientSecret, subjectToken)
client := http.Client{}
authConfig, err := NewOIDCAuthConfig(context.TODO(), &client, "http://"+host+":"+port, realm, clientID, clientSecret, subjectToken)
if err != nil {
t.Fatalf("authconfig failed: %v", err)
}
Expand Down
21 changes: 13 additions & 8 deletions sdk/idp_access_token_source.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package sdk

import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
"sync"
Expand All @@ -16,6 +18,7 @@ import (
"github.com/opentdf/platform/lib/ocrypto"
"github.com/opentdf/platform/sdk/auth"
"github.com/opentdf/platform/sdk/internal/oauth"
"golang.org/x/oauth2"
)

const (
Expand Down Expand Up @@ -74,10 +77,8 @@ func getNewDPoPKey() (string, jwk.Key, *ocrypto.AsymDecryption, error) { //nolin
return dpopPublicKeyPEM.String(), dpopKey, &asymDecryption, nil
}

/*
Credentials that allow us to connect to an IDP and obtain an access token that is bound
to a DPoP key
*/
// IDPAccessTokenSource credentials that allow us to connect to an IDP and obtain an access token that is bound
// to a DPoP key
type IDPAccessTokenSource struct {
credentials oauth.ClientCredentials
idpTokenEndpoint url.URL
Expand All @@ -89,6 +90,10 @@ type IDPAccessTokenSource struct {
tokenMutex *sync.Mutex
}

func (t *IDPAccessTokenSource) Token() (*oauth2.Token, error) {
return nil, fmt.Errorf("unimplemented")
}

func NewIDPAccessTokenSource(
credentials oauth.ClientCredentials, idpTokenEndpoint string, scopes []string) (*IDPAccessTokenSource, error) {
endpoint, err := url.Parse(idpTokenEndpoint)
Expand All @@ -115,14 +120,14 @@ func NewIDPAccessTokenSource(
return &tokenSource, nil
}

// use a pointer receiver so that the token state is shared
func (t *IDPAccessTokenSource) AccessToken() (auth.AccessToken, error) {
// AccessToken use a pointer receiver so that the token state is shared
func (t *IDPAccessTokenSource) AccessToken(ctx context.Context, client *http.Client) (auth.AccessToken, error) {
t.tokenMutex.Lock()
defer t.tokenMutex.Unlock()

if t.token == nil || t.token.Expired() {
slog.Debug("getting new access token")
tok, err := oauth.GetAccessToken(t.idpTokenEndpoint.String(), t.scopes, t.credentials, t.dpopKey)
slog.DebugContext(ctx, "getting new access token")
tok, err := oauth.GetAccessToken(client, t.idpTokenEndpoint.String(), t.scopes, t.credentials, t.dpopKey)
if err != nil {
return "", fmt.Errorf("error getting access token: %w", err)
}
Expand Down

0 comments on commit 5c94d02

Please sign in to comment.