2016-07-26 01:30:28 +05:30
|
|
|
package kubernetes
|
|
|
|
|
|
|
|
import (
|
2016-10-06 04:34:48 +05:30
|
|
|
"encoding/base32"
|
|
|
|
"strings"
|
2016-07-26 01:30:28 +05:30
|
|
|
"time"
|
|
|
|
|
|
|
|
jose "gopkg.in/square/go-jose.v2"
|
|
|
|
|
2016-08-11 11:01:42 +05:30
|
|
|
"github.com/coreos/dex/storage"
|
|
|
|
"github.com/coreos/dex/storage/kubernetes/k8sapi"
|
2016-07-26 01:30:28 +05:30
|
|
|
)
|
|
|
|
|
2016-10-14 05:20:20 +05:30
|
|
|
var tprMeta = k8sapi.TypeMeta{
|
|
|
|
APIVersion: "extensions/v1beta1",
|
|
|
|
Kind: "ThirdPartyResource",
|
|
|
|
}
|
|
|
|
|
|
|
|
// The set of third party resources required by the storage. These are managed by
|
|
|
|
// the storage so it can migrate itself by creating new resources.
|
|
|
|
var thirdPartyResources = []k8sapi.ThirdPartyResource{
|
|
|
|
{
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: "auth-code.oidc.coreos.com",
|
|
|
|
},
|
|
|
|
TypeMeta: tprMeta,
|
|
|
|
Description: "A code which can be claimed for an access token.",
|
|
|
|
Versions: []k8sapi.APIVersion{{Name: "v1"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: "auth-request.oidc.coreos.com",
|
|
|
|
},
|
|
|
|
TypeMeta: tprMeta,
|
|
|
|
Description: "A request for an end user to authorize a client.",
|
|
|
|
Versions: []k8sapi.APIVersion{{Name: "v1"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: "o-auth2-client.oidc.coreos.com",
|
|
|
|
},
|
|
|
|
TypeMeta: tprMeta,
|
|
|
|
Description: "An OpenID Connect client.",
|
|
|
|
Versions: []k8sapi.APIVersion{{Name: "v1"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: "signing-key.oidc.coreos.com",
|
|
|
|
},
|
|
|
|
TypeMeta: tprMeta,
|
|
|
|
Description: "Keys used to sign and verify OpenID Connect tokens.",
|
|
|
|
Versions: []k8sapi.APIVersion{{Name: "v1"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: "refresh-token.oidc.coreos.com",
|
|
|
|
},
|
|
|
|
TypeMeta: tprMeta,
|
|
|
|
Description: "Refresh tokens for clients to continuously act on behalf of an end user.",
|
|
|
|
Versions: []k8sapi.APIVersion{{Name: "v1"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: "password.oidc.coreos.com",
|
|
|
|
},
|
|
|
|
TypeMeta: tprMeta,
|
|
|
|
Description: "Passwords managed by the OIDC server.",
|
|
|
|
Versions: []k8sapi.APIVersion{{Name: "v1"}},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-07-26 01:30:28 +05:30
|
|
|
// There will only ever be a single keys resource. Maintain this by setting a
|
|
|
|
// common name.
|
|
|
|
const keysName = "openid-connect-keys"
|
|
|
|
|
|
|
|
// Client is a mirrored struct from storage with JSON struct tags and
|
|
|
|
// Kubernetes type metadata.
|
2016-08-01 11:55:06 +05:30
|
|
|
//
|
|
|
|
// TODO(ericchiang): Kubernetes has an extremely restricted set of characters it can use for IDs.
|
|
|
|
// Consider base32ing client IDs.
|
2016-07-26 01:30:28 +05:30
|
|
|
type Client struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
|
|
|
|
|
|
|
Secret string `json:"secret,omitempty"`
|
|
|
|
RedirectURIs []string `json:"redirectURIs,omitempty"`
|
|
|
|
TrustedPeers []string `json:"trustedPeers,omitempty"`
|
|
|
|
|
|
|
|
Public bool `json:"public"`
|
|
|
|
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
LogoURL string `json:"logoURL,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClientList is a list of Clients.
|
|
|
|
type ClientList struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ListMeta `json:"metadata,omitempty"`
|
|
|
|
Clients []Client `json:"items"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *client) fromStorageClient(c storage.Client) Client {
|
|
|
|
return Client{
|
|
|
|
TypeMeta: k8sapi.TypeMeta{
|
|
|
|
Kind: kindClient,
|
2016-10-14 05:20:20 +05:30
|
|
|
APIVersion: cli.apiVersion,
|
2016-07-26 01:30:28 +05:30
|
|
|
},
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: c.ID,
|
|
|
|
Namespace: cli.namespace,
|
|
|
|
},
|
|
|
|
Secret: c.Secret,
|
|
|
|
RedirectURIs: c.RedirectURIs,
|
|
|
|
TrustedPeers: c.TrustedPeers,
|
|
|
|
Public: c.Public,
|
|
|
|
Name: c.Name,
|
|
|
|
LogoURL: c.LogoURL,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toStorageClient(c Client) storage.Client {
|
|
|
|
return storage.Client{
|
|
|
|
ID: c.ObjectMeta.Name,
|
|
|
|
Secret: c.Secret,
|
|
|
|
RedirectURIs: c.RedirectURIs,
|
|
|
|
TrustedPeers: c.TrustedPeers,
|
|
|
|
Public: c.Public,
|
|
|
|
Name: c.Name,
|
|
|
|
LogoURL: c.LogoURL,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-03 10:27:36 +05:30
|
|
|
// Claims is a mirrored struct from storage with JSON struct tags.
|
|
|
|
type Claims struct {
|
2016-07-26 01:30:28 +05:30
|
|
|
UserID string `json:"userID"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
EmailVerified bool `json:"emailVerified"`
|
|
|
|
Groups []string `json:"groups,omitempty"`
|
|
|
|
}
|
|
|
|
|
2016-08-03 10:27:36 +05:30
|
|
|
func fromStorageClaims(i storage.Claims) Claims {
|
|
|
|
return Claims{
|
2016-07-26 01:30:28 +05:30
|
|
|
UserID: i.UserID,
|
|
|
|
Username: i.Username,
|
|
|
|
Email: i.Email,
|
|
|
|
EmailVerified: i.EmailVerified,
|
|
|
|
Groups: i.Groups,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-03 10:27:36 +05:30
|
|
|
func toStorageClaims(i Claims) storage.Claims {
|
|
|
|
return storage.Claims{
|
2016-07-26 01:30:28 +05:30
|
|
|
UserID: i.UserID,
|
|
|
|
Username: i.Username,
|
|
|
|
Email: i.Email,
|
|
|
|
EmailVerified: i.EmailVerified,
|
|
|
|
Groups: i.Groups,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AuthRequest is a mirrored struct from storage with JSON struct tags and
|
|
|
|
// Kubernetes type metadata.
|
|
|
|
type AuthRequest struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
|
|
|
|
|
|
|
ClientID string `json:"clientID"`
|
|
|
|
ResponseTypes []string `json:"responseTypes,omitempty"`
|
|
|
|
Scopes []string `json:"scopes,omitempty"`
|
|
|
|
RedirectURI string `json:"redirectURI"`
|
|
|
|
|
|
|
|
Nonce string `json:"nonce,omitempty"`
|
|
|
|
State string `json:"state,omitempty"`
|
|
|
|
|
|
|
|
// The client has indicated that the end user must be shown an approval prompt
|
|
|
|
// on all requests. The server cannot cache their initial action for subsequent
|
|
|
|
// attempts.
|
|
|
|
ForceApprovalPrompt bool `json:"forceApprovalPrompt,omitempty"`
|
|
|
|
|
2016-09-15 05:08:12 +05:30
|
|
|
LoggedIn bool `json:"loggedIn"`
|
|
|
|
|
2016-07-26 01:30:28 +05:30
|
|
|
// The identity of the end user. Generally nil until the user authenticates
|
|
|
|
// with a backend.
|
2016-09-15 05:08:12 +05:30
|
|
|
Claims Claims `json:"claims,omitempty"`
|
2016-07-26 01:30:28 +05:30
|
|
|
// The connector used to login the user. Set when the user authenticates.
|
2016-08-03 09:44:24 +05:30
|
|
|
ConnectorID string `json:"connectorID,omitempty"`
|
|
|
|
ConnectorData []byte `json:"connectorData,omitempty"`
|
2016-07-26 01:30:28 +05:30
|
|
|
|
|
|
|
Expiry time.Time `json:"expiry"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// AuthRequestList is a list of AuthRequests.
|
|
|
|
type AuthRequestList struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ListMeta `json:"metadata,omitempty"`
|
|
|
|
AuthRequests []AuthRequest `json:"items"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func toStorageAuthRequest(req AuthRequest) storage.AuthRequest {
|
|
|
|
a := storage.AuthRequest{
|
|
|
|
ID: req.ObjectMeta.Name,
|
|
|
|
ClientID: req.ClientID,
|
|
|
|
ResponseTypes: req.ResponseTypes,
|
|
|
|
Scopes: req.Scopes,
|
|
|
|
RedirectURI: req.RedirectURI,
|
|
|
|
Nonce: req.Nonce,
|
|
|
|
State: req.State,
|
|
|
|
ForceApprovalPrompt: req.ForceApprovalPrompt,
|
2016-09-15 05:08:12 +05:30
|
|
|
LoggedIn: req.LoggedIn,
|
2016-07-26 01:30:28 +05:30
|
|
|
ConnectorID: req.ConnectorID,
|
2016-08-03 09:44:24 +05:30
|
|
|
ConnectorData: req.ConnectorData,
|
2016-07-26 01:30:28 +05:30
|
|
|
Expiry: req.Expiry,
|
2016-09-15 05:08:12 +05:30
|
|
|
Claims: toStorageClaims(req.Claims),
|
2016-07-26 01:30:28 +05:30
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest {
|
|
|
|
req := AuthRequest{
|
|
|
|
TypeMeta: k8sapi.TypeMeta{
|
|
|
|
Kind: kindAuthRequest,
|
2016-10-14 05:20:20 +05:30
|
|
|
APIVersion: cli.apiVersion,
|
2016-07-26 01:30:28 +05:30
|
|
|
},
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: a.ID,
|
|
|
|
Namespace: cli.namespace,
|
|
|
|
},
|
|
|
|
ClientID: a.ClientID,
|
|
|
|
ResponseTypes: a.ResponseTypes,
|
|
|
|
Scopes: a.Scopes,
|
|
|
|
RedirectURI: a.RedirectURI,
|
|
|
|
Nonce: a.Nonce,
|
|
|
|
State: a.State,
|
2016-09-15 05:08:12 +05:30
|
|
|
LoggedIn: a.LoggedIn,
|
2016-07-26 01:30:28 +05:30
|
|
|
ForceApprovalPrompt: a.ForceApprovalPrompt,
|
|
|
|
ConnectorID: a.ConnectorID,
|
2016-08-03 09:44:24 +05:30
|
|
|
ConnectorData: a.ConnectorData,
|
2016-07-26 01:30:28 +05:30
|
|
|
Expiry: a.Expiry,
|
2016-09-15 05:08:12 +05:30
|
|
|
Claims: fromStorageClaims(a.Claims),
|
2016-07-26 01:30:28 +05:30
|
|
|
}
|
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
2016-10-06 04:34:48 +05:30
|
|
|
// Password is a mirrored struct from the stroage with JSON struct tags and
|
|
|
|
// Kubernetes type metadata.
|
|
|
|
type Password struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
|
|
|
|
|
|
|
// The Kubernetes name is actually an encoded version of this value.
|
|
|
|
//
|
|
|
|
// This field is IMMUTABLE. Do not change.
|
|
|
|
Email string `json:"email,omitempty"`
|
|
|
|
|
|
|
|
Hash []byte `json:"hash,omitempty"`
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
UserID string `json:"userID,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kubernetes only allows lower case letters for names.
|
|
|
|
//
|
|
|
|
// NOTE(ericchiang): This is currently copied from the storage package's NewID()
|
|
|
|
// method. Once we refactor those into the storage, just use that instead.
|
|
|
|
var encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
|
|
|
|
|
|
|
|
// Map an arbitrary email to a valid Kuberntes name.
|
|
|
|
func emailToID(email string) string {
|
|
|
|
return strings.TrimRight(encoding.EncodeToString([]byte(strings.ToLower(email))), "=")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *client) fromStoragePassword(p storage.Password) Password {
|
|
|
|
email := strings.ToLower(p.Email)
|
|
|
|
return Password{
|
|
|
|
TypeMeta: k8sapi.TypeMeta{
|
|
|
|
Kind: kindPassword,
|
2016-10-14 05:20:20 +05:30
|
|
|
APIVersion: cli.apiVersion,
|
2016-10-06 04:34:48 +05:30
|
|
|
},
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: emailToID(email),
|
|
|
|
Namespace: cli.namespace,
|
|
|
|
},
|
|
|
|
Email: email,
|
|
|
|
Hash: p.Hash,
|
|
|
|
Username: p.Username,
|
|
|
|
UserID: p.UserID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toStoragePassword(p Password) storage.Password {
|
|
|
|
return storage.Password{
|
|
|
|
Email: p.Email,
|
|
|
|
Hash: p.Hash,
|
|
|
|
Username: p.Username,
|
|
|
|
UserID: p.UserID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-26 01:30:28 +05:30
|
|
|
// AuthCode is a mirrored struct from storage with JSON struct tags and
|
|
|
|
// Kubernetes type metadata.
|
|
|
|
type AuthCode struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
|
|
|
|
|
|
|
ClientID string `json:"clientID"`
|
|
|
|
Scopes []string `json:"scopes,omitempty"`
|
|
|
|
RedirectURI string `json:"redirectURI"`
|
|
|
|
|
|
|
|
Nonce string `json:"nonce,omitempty"`
|
|
|
|
State string `json:"state,omitempty"`
|
|
|
|
|
2016-08-03 10:27:36 +05:30
|
|
|
Claims Claims `json:"claims,omitempty"`
|
2016-08-03 09:44:24 +05:30
|
|
|
|
|
|
|
ConnectorID string `json:"connectorID,omitempty"`
|
|
|
|
ConnectorData []byte `json:"connectorData,omitempty"`
|
2016-07-26 01:30:28 +05:30
|
|
|
|
|
|
|
Expiry time.Time `json:"expiry"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// AuthCodeList is a list of AuthCodes.
|
|
|
|
type AuthCodeList struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ListMeta `json:"metadata,omitempty"`
|
|
|
|
AuthCodes []AuthCode `json:"items"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *client) fromStorageAuthCode(a storage.AuthCode) AuthCode {
|
|
|
|
return AuthCode{
|
|
|
|
TypeMeta: k8sapi.TypeMeta{
|
|
|
|
Kind: kindAuthCode,
|
2016-10-14 05:20:20 +05:30
|
|
|
APIVersion: cli.apiVersion,
|
2016-07-26 01:30:28 +05:30
|
|
|
},
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: a.ID,
|
|
|
|
Namespace: cli.namespace,
|
|
|
|
},
|
2016-08-03 09:44:24 +05:30
|
|
|
ClientID: a.ClientID,
|
|
|
|
RedirectURI: a.RedirectURI,
|
|
|
|
ConnectorID: a.ConnectorID,
|
|
|
|
ConnectorData: a.ConnectorData,
|
|
|
|
Nonce: a.Nonce,
|
|
|
|
Scopes: a.Scopes,
|
2016-08-03 10:27:36 +05:30
|
|
|
Claims: fromStorageClaims(a.Claims),
|
2016-08-03 09:44:24 +05:30
|
|
|
Expiry: a.Expiry,
|
2016-07-26 01:30:28 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toStorageAuthCode(a AuthCode) storage.AuthCode {
|
|
|
|
return storage.AuthCode{
|
2016-08-03 09:44:24 +05:30
|
|
|
ID: a.ObjectMeta.Name,
|
|
|
|
ClientID: a.ClientID,
|
|
|
|
RedirectURI: a.RedirectURI,
|
|
|
|
ConnectorID: a.ConnectorID,
|
|
|
|
ConnectorData: a.ConnectorData,
|
|
|
|
Nonce: a.Nonce,
|
|
|
|
Scopes: a.Scopes,
|
2016-08-03 10:27:36 +05:30
|
|
|
Claims: toStorageClaims(a.Claims),
|
2016-08-03 09:44:24 +05:30
|
|
|
Expiry: a.Expiry,
|
2016-07-26 01:30:28 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-03 10:27:36 +05:30
|
|
|
// RefreshToken is a mirrored struct from storage with JSON struct tags and
|
2016-07-26 01:30:28 +05:30
|
|
|
// Kubernetes type metadata.
|
2016-08-03 10:27:36 +05:30
|
|
|
type RefreshToken struct {
|
2016-07-26 01:30:28 +05:30
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
|
|
|
|
|
|
|
ClientID string `json:"clientID"`
|
|
|
|
Scopes []string `json:"scopes,omitempty"`
|
|
|
|
|
|
|
|
Nonce string `json:"nonce,omitempty"`
|
|
|
|
|
2016-08-03 10:27:36 +05:30
|
|
|
Claims Claims `json:"claims,omitempty"`
|
|
|
|
ConnectorID string `json:"connectorID,omitempty"`
|
2016-07-26 01:30:28 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
// RefreshList is a list of refresh tokens.
|
|
|
|
type RefreshList struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ListMeta `json:"metadata,omitempty"`
|
2016-08-03 10:27:36 +05:30
|
|
|
RefreshTokens []RefreshToken `json:"items"`
|
2016-07-26 01:30:28 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
// Keys is a mirrored struct from storage with JSON struct tags and Kubernetes
|
|
|
|
// type metadata.
|
|
|
|
type Keys struct {
|
|
|
|
k8sapi.TypeMeta `json:",inline"`
|
|
|
|
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
|
|
|
|
|
|
|
// Key for creating and verifying signatures. These may be nil.
|
|
|
|
SigningKey *jose.JSONWebKey `json:"signingKey,omitempty"`
|
|
|
|
SigningKeyPub *jose.JSONWebKey `json:"signingKeyPub,omitempty"`
|
|
|
|
// Old signing keys which have been rotated but can still be used to validate
|
|
|
|
// existing signatures.
|
|
|
|
VerificationKeys []storage.VerificationKey `json:"verificationKeys,omitempty"`
|
|
|
|
|
|
|
|
// The next time the signing key will rotate.
|
|
|
|
//
|
|
|
|
// For caching purposes, implementations MUST NOT update keys before this time.
|
|
|
|
NextRotation time.Time `json:"nextRotation"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *client) fromStorageKeys(keys storage.Keys) Keys {
|
|
|
|
return Keys{
|
|
|
|
TypeMeta: k8sapi.TypeMeta{
|
|
|
|
Kind: kindKeys,
|
2016-10-14 05:20:20 +05:30
|
|
|
APIVersion: cli.apiVersion,
|
2016-07-26 01:30:28 +05:30
|
|
|
},
|
|
|
|
ObjectMeta: k8sapi.ObjectMeta{
|
|
|
|
Name: keysName,
|
|
|
|
Namespace: cli.namespace,
|
|
|
|
},
|
|
|
|
SigningKey: keys.SigningKey,
|
|
|
|
SigningKeyPub: keys.SigningKeyPub,
|
|
|
|
VerificationKeys: keys.VerificationKeys,
|
|
|
|
NextRotation: keys.NextRotation,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toStorageKeys(keys Keys) storage.Keys {
|
|
|
|
return storage.Keys{
|
|
|
|
SigningKey: keys.SigningKey,
|
|
|
|
SigningKeyPub: keys.SigningKeyPub,
|
|
|
|
VerificationKeys: keys.VerificationKeys,
|
|
|
|
NextRotation: keys.NextRotation,
|
|
|
|
}
|
|
|
|
}
|