forked from mystiq/dex
*: add password resource to kubernetes storage implementation
This commit is contained in:
parent
ae3b5ef6e9
commit
7ff3ce85a2
6 changed files with 145 additions and 7 deletions
|
@ -46,3 +46,12 @@ kind: ThirdPartyResource
|
||||||
description: "Refresh tokens for clients to continuously act on behalf of an end user."
|
description: "Refresh tokens for clients to continuously act on behalf of an end user."
|
||||||
versions:
|
versions:
|
||||||
- name: v1
|
- name: v1
|
||||||
|
---
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
name: password.passwords.oidc.coreos.com
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: ThirdPartyResource
|
||||||
|
description: "Passwords managed by the OIDC server."
|
||||||
|
versions:
|
||||||
|
- name: v1
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -165,6 +166,26 @@ func (c *client) delete(resource, name string) error {
|
||||||
return checkHTTPErr(resp, http.StatusOK)
|
return checkHTTPErr(resp, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *client) deleteAll(resource string) error {
|
||||||
|
var list struct {
|
||||||
|
k8sapi.TypeMeta `json:",inline"`
|
||||||
|
k8sapi.ListMeta `json:"metadata,omitempty"`
|
||||||
|
Items []struct {
|
||||||
|
k8sapi.TypeMeta `json:",inline"`
|
||||||
|
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
||||||
|
if err := c.list(resource, &list); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range list.Items {
|
||||||
|
if err := c.delete(resource, item.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *client) put(resource, name string, v interface{}) error {
|
func (c *client) put(resource, name string, v interface{}) error {
|
||||||
body, err := json.Marshal(v)
|
body, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -190,9 +211,9 @@ func (c *client) put(resource, name string, v interface{}) error {
|
||||||
|
|
||||||
func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string) (*client, error) {
|
func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string) (*client, error) {
|
||||||
tlsConfig := cryptopasta.DefaultTLSConfig()
|
tlsConfig := cryptopasta.DefaultTLSConfig()
|
||||||
data := func(b []byte, file string) ([]byte, error) {
|
data := func(b string, file string) ([]byte, error) {
|
||||||
if b != nil {
|
if b != "" {
|
||||||
return b, nil
|
return base64.StdEncoding.DecodeString(b)
|
||||||
}
|
}
|
||||||
if file == "" {
|
if file == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -62,7 +62,9 @@ type Cluster struct {
|
||||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||||
CertificateAuthority string `yaml:"certificate-authority,omitempty"`
|
CertificateAuthority string `yaml:"certificate-authority,omitempty"`
|
||||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||||
CertificateAuthorityData []byte `yaml:"certificate-authority-data,omitempty"`
|
//
|
||||||
|
// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
|
||||||
|
CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"`
|
||||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||||
Extensions []NamedExtension `yaml:"extensions,omitempty"`
|
Extensions []NamedExtension `yaml:"extensions,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -72,11 +74,15 @@ type AuthInfo struct {
|
||||||
// ClientCertificate is the path to a client cert file for TLS.
|
// ClientCertificate is the path to a client cert file for TLS.
|
||||||
ClientCertificate string `yaml:"client-certificate,omitempty"`
|
ClientCertificate string `yaml:"client-certificate,omitempty"`
|
||||||
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||||
ClientCertificateData []byte `yaml:"client-certificate-data,omitempty"`
|
//
|
||||||
|
// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
|
||||||
|
ClientCertificateData string `yaml:"client-certificate-data,omitempty"`
|
||||||
// ClientKey is the path to a client key file for TLS.
|
// ClientKey is the path to a client key file for TLS.
|
||||||
ClientKey string `yaml:"client-key,omitempty"`
|
ClientKey string `yaml:"client-key,omitempty"`
|
||||||
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||||
ClientKeyData []byte `yaml:"client-key-data,omitempty"`
|
//
|
||||||
|
// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
|
||||||
|
ClientKeyData string `yaml:"client-key-data,omitempty"`
|
||||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||||
Token string `yaml:"token,omitempty"`
|
Token string `yaml:"token,omitempty"`
|
||||||
// Impersonate is the username to imperonate. The name matches the flag.
|
// Impersonate is the username to imperonate. The name matches the flag.
|
||||||
|
|
|
@ -20,6 +20,7 @@ const (
|
||||||
kindClient = "OAuth2Client"
|
kindClient = "OAuth2Client"
|
||||||
kindRefreshToken = "RefreshToken"
|
kindRefreshToken = "RefreshToken"
|
||||||
kindKeys = "SigningKey"
|
kindKeys = "SigningKey"
|
||||||
|
kindPassword = "Password"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,6 +29,7 @@ const (
|
||||||
resourceClient = "oauth2clients"
|
resourceClient = "oauth2clients"
|
||||||
resourceRefreshToken = "refreshtokens"
|
resourceRefreshToken = "refreshtokens"
|
||||||
resourceKeys = "signingkeies" // Kubernetes attempts to pluralize.
|
resourceKeys = "signingkeies" // Kubernetes attempts to pluralize.
|
||||||
|
resourcePassword = "passwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config values for the Kubernetes storage type.
|
// Config values for the Kubernetes storage type.
|
||||||
|
@ -109,6 +111,10 @@ func (cli *client) CreateAuthCode(c storage.AuthCode) error {
|
||||||
return cli.post(resourceAuthCode, cli.fromStorageAuthCode(c))
|
return cli.post(resourceAuthCode, cli.fromStorageAuthCode(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *client) CreatePassword(p storage.Password) error {
|
||||||
|
return cli.post(resourcePassword, cli.fromStoragePassword(p))
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *client) CreateRefresh(r storage.RefreshToken) error {
|
func (cli *client) CreateRefresh(r storage.RefreshToken) error {
|
||||||
refresh := RefreshToken{
|
refresh := RefreshToken{
|
||||||
TypeMeta: k8sapi.TypeMeta{
|
TypeMeta: k8sapi.TypeMeta{
|
||||||
|
@ -152,6 +158,14 @@ func (cli *client) GetClient(id string) (storage.Client, error) {
|
||||||
return toStorageClient(c), nil
|
return toStorageClient(c), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *client) GetPassword(email string) (storage.Password, error) {
|
||||||
|
var p Password
|
||||||
|
if err := cli.get(resourcePassword, emailToID(email), &p); err != nil {
|
||||||
|
return storage.Password{}, err
|
||||||
|
}
|
||||||
|
return toStoragePassword(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *client) GetKeys() (storage.Keys, error) {
|
func (cli *client) GetKeys() (storage.Keys, error) {
|
||||||
var keys Keys
|
var keys Keys
|
||||||
if err := cli.get(resourceKeys, keysName, &keys); err != nil {
|
if err := cli.get(resourceKeys, keysName, &keys); err != nil {
|
||||||
|
@ -199,6 +213,10 @@ func (cli *client) DeleteRefresh(id string) error {
|
||||||
return cli.delete(resourceRefreshToken, id)
|
return cli.delete(resourceRefreshToken, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *client) DeletePassword(email string) error {
|
||||||
|
return cli.delete(resourcePassword, emailToID(email))
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *client) UpdateClient(id string, updater func(old storage.Client) (storage.Client, error)) error {
|
func (cli *client) UpdateClient(id string, updater func(old storage.Client) (storage.Client, error)) error {
|
||||||
var c Client
|
var c Client
|
||||||
if err := cli.get(resourceClient, id, &c); err != nil {
|
if err := cli.get(resourceClient, id, &c); err != nil {
|
||||||
|
@ -214,6 +232,23 @@ func (cli *client) UpdateClient(id string, updater func(old storage.Client) (sto
|
||||||
return cli.put(resourceClient, id, newClient)
|
return cli.put(resourceClient, id, newClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *client) UpdatePassword(email string, updater func(old storage.Password) (storage.Password, error)) error {
|
||||||
|
id := emailToID(email)
|
||||||
|
var p Password
|
||||||
|
if err := cli.get(resourcePassword, id, &p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := updater(toStoragePassword(p))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newPassword := cli.fromStoragePassword(updated)
|
||||||
|
newPassword.ObjectMeta = p.ObjectMeta
|
||||||
|
return cli.put(resourcePassword, id, newPassword)
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *client) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) error {
|
func (cli *client) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) error {
|
||||||
firstUpdate := false
|
firstUpdate := false
|
||||||
var keys Keys
|
var keys Keys
|
||||||
|
|
|
@ -75,7 +75,18 @@ func TestURLFor(t *testing.T) {
|
||||||
func TestStorage(t *testing.T) {
|
func TestStorage(t *testing.T) {
|
||||||
client := loadClient(t)
|
client := loadClient(t)
|
||||||
conformance.RunTestSuite(t, func() storage.Storage {
|
conformance.RunTestSuite(t, func() storage.Storage {
|
||||||
// TODO(erichiang): Tear down namespaces between each iteration.
|
for _, resource := range []string{
|
||||||
|
resourceAuthCode,
|
||||||
|
resourceAuthRequest,
|
||||||
|
resourceClient,
|
||||||
|
resourceRefreshToken,
|
||||||
|
resourceKeys,
|
||||||
|
resourcePassword,
|
||||||
|
} {
|
||||||
|
if err := client.deleteAll(resource); err != nil {
|
||||||
|
t.Fatalf("delete all %q failed: %v", resource, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return client
|
return client
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base32"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "gopkg.in/square/go-jose.v2"
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
@ -182,6 +184,60 @@ func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest {
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
APIVersion: cli.apiVersionForResource(resourcePassword),
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AuthCode is a mirrored struct from storage with JSON struct tags and
|
// AuthCode is a mirrored struct from storage with JSON struct tags and
|
||||||
// Kubernetes type metadata.
|
// Kubernetes type metadata.
|
||||||
type AuthCode struct {
|
type AuthCode struct {
|
||||||
|
|
Loading…
Reference in a new issue