Created
September 7, 2022 16:43
-
-
Save mjpitz/de41998b0367b250f0701bd0cd72654a to your computer and use it in GitHub Desktop.
Some rough code used to obtain access to storj using OIDC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package uplink | |
import ( | |
"context" | |
"crypto/aes" | |
"crypto/sha256" | |
"encoding/json" | |
"net/http" | |
"github.com/zeebo/errs" | |
"golang.org/x/oauth2" | |
"storj.io/common/base58" | |
"storj.io/common/grant" | |
"storj.io/common/macaroon" | |
"storj.io/common/storj" | |
"storj.io/common/uuid" | |
) | |
type discoveryConfig struct { | |
NodeURL string `json:"node_url"` | |
Issuer string `json:"issuer"` | |
AuthURL string `json:"authorization_endpoint"` | |
TokenURL string `json:"token_endpoint"` | |
UserInfoURL string `json:"userinfo_endpoint"` | |
} | |
type userInfo struct { | |
Subject uuid.UUID `json:"sub"` | |
Email string `json:"email"` | |
EmailVerified bool `json:"email_verified"` | |
// custom values below | |
Cubbyhole string `json:"cubbyhole"` | |
} | |
func discoverConfig(ctx context.Context, baseURL string) (*discoveryConfig, error) { | |
client := http.DefaultClient | |
resp, err := client.Get(baseURL + "/.well-known/openid-configuration") | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
defer func() { _ = resp.Body.Close() }() | |
discovery := &discoveryConfig{} | |
err = json.NewDecoder(resp.Body).Decode(discovery) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
return discovery, nil | |
} | |
func fetchUser(client *http.Client, userInfoURL string) (*userInfo, error) { | |
resp, err := client.Get(userInfoURL) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
defer func() { _ = resp.Body.Close() }() | |
info := &userInfo{} | |
err = json.NewDecoder(resp.Body).Decode(info) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
return info, nil | |
} | |
func UsingOIDC(ctx context.Context, baseURL string, config oauth2.Config) (*OIDC, error) { | |
discovery, err := discoverConfig(ctx, baseURL) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
satelliteAddress, err := storj.ParseNodeURL(discovery.NodeURL) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
config.Endpoint = oauth2.Endpoint{ | |
AuthURL: discovery.AuthURL, | |
TokenURL: discovery.TokenURL, | |
} | |
return &OIDC{ | |
satelliteURL: satelliteAddress, | |
config: &config, | |
userInfoURL: discovery.UserInfoURL, | |
}, nil | |
} | |
// OIDC provides functionality for interacting with Storj's OpenID provider. Not all functionality has been implemented | |
// yet, but the minimal components needed to develop and deploy applications are. | |
type OIDC struct { | |
satelliteURL storj.NodeURL | |
config *oauth2.Config | |
userInfoURL string | |
} | |
// AuthCodeURL is a variant of the oauth2.Config AuthCodeURL method that allows an encryption key to be provided. The | |
// encryption key can be used to encrypt a cubbyhole value coming from the client side of an upstream OAuth2 provider. | |
func (c *OIDC) AuthCodeURL(state, encryptionKey string) string { | |
return c.config.AuthCodeURL(state) + "#" + encryptionKey | |
} | |
// Exchange simply wraps the underlying oauth2.Config Exchange method. This function does not mutate the response as | |
// refresh tokens can be used to re-obtain access. The resulting oauth2.Token can be stored for later refreshed using | |
// the TokenSource method call. | |
func (c *OIDC) Exchange(ctx context.Context, code string) (*oauth2.Token, error) { | |
token, err := c.config.Exchange(ctx, code) | |
if err != nil { | |
return nil, errs.Wrap(err) | |
} | |
return token, nil | |
} | |
// TokenSource provides a way to obtain a refreshing token source given the current configuration. This allows new | |
// oauth2.Token to be obtained from the server using a refreshing workflow. | |
func (c *OIDC) TokenSource(ctx context.Context, token *oauth2.Token) oauth2.TokenSource { | |
return c.config.TokenSource(ctx, token) | |
} | |
// Access returns an access grant that's used by the | |
func (c *OIDC) Access(ctx context.Context, tokenSource oauth2.TokenSource, encryptionKey string) (access *Access, err error) { | |
token, err := tokenSource.Token() | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
info, err := fetchUser(oauth2.NewClient(ctx, tokenSource), c.userInfoURL) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
apiKey, err := macaroon.ParseAPIKey(token.AccessToken) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
// in practice, you should decrypt the cubbyhole and pass it here | |
encryptedCubbyhole := base58.Decode(info.Cubbyhole) | |
aesKey := sha256.Sum256([]byte(encryptionKey)) | |
cipher, err := aes.NewCipher(aesKey[:]) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
rootKey := make([]byte, len(encryptedCubbyhole)) | |
cipher.Decrypt(rootKey, encryptedCubbyhole) | |
key, err := storj.NewKey(rootKey) | |
if err != nil { | |
return nil, packageError.Wrap(err) | |
} | |
encAccess := grant.NewEncryptionAccessWithDefaultKey(key) | |
encAccess.LimitTo(apiKey) | |
encAccess.SetDefaultKey(key) | |
encAccess.SetDefaultPathCipher(storj.EncAESGCM) | |
return &Access{ | |
satelliteURL: c.satelliteURL, | |
apiKey: apiKey, | |
encAccess: encAccess, | |
}, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment