89 lines
3.1 KiB
Go
89 lines
3.1 KiB
Go
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package client is a wrapper for interacting with Pinniped's CredentialRequest API.
|
|
package client
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
|
|
"go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1"
|
|
"go.pinniped.dev/generated/1.20/client/concierge/clientset/versioned"
|
|
)
|
|
|
|
// ErrLoginFailed is returned by ExchangeToken when the server rejects the login request.
|
|
var ErrLoginFailed = errors.New("login failed")
|
|
|
|
// ExchangeToken exchanges an opaque token using the Pinniped TokenCredentialRequest API, returning a client-go ExecCredential valid on the target cluster.
|
|
func ExchangeToken(ctx context.Context, namespace string, authenticator corev1.TypedLocalObjectReference, token string, caBundle string, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
|
|
client, err := getClient(apiEndpoint, caBundle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get API client: %w", err)
|
|
}
|
|
|
|
resp, err := client.LoginV1alpha1().TokenCredentialRequests(namespace).Create(ctx, &v1alpha1.TokenCredentialRequest{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: namespace,
|
|
},
|
|
Spec: v1alpha1.TokenCredentialRequestSpec{
|
|
Token: token,
|
|
Authenticator: authenticator,
|
|
},
|
|
}, metav1.CreateOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not login: %w", err)
|
|
}
|
|
if resp.Status.Credential == nil || resp.Status.Message != nil {
|
|
if resp.Status.Message != nil {
|
|
return nil, fmt.Errorf("%w: %s", ErrLoginFailed, *resp.Status.Message)
|
|
}
|
|
return nil, fmt.Errorf("%w: unknown", ErrLoginFailed)
|
|
}
|
|
|
|
return &clientauthenticationv1beta1.ExecCredential{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "ExecCredential",
|
|
APIVersion: "client.authentication.k8s.io/v1beta1",
|
|
},
|
|
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
|
|
ExpirationTimestamp: &resp.Status.Credential.ExpirationTimestamp,
|
|
ClientCertificateData: resp.Status.Credential.ClientCertificateData,
|
|
ClientKeyData: resp.Status.Credential.ClientKeyData,
|
|
Token: resp.Status.Credential.Token,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// getClient returns an anonymous client for the Pinniped API at the provided endpoint/CA bundle.
|
|
func getClient(apiEndpoint string, caBundle string) (versioned.Interface, error) {
|
|
cfg, err := clientcmd.NewNonInteractiveClientConfig(clientcmdapi.Config{
|
|
Clusters: map[string]*clientcmdapi.Cluster{
|
|
"cluster": {
|
|
Server: apiEndpoint,
|
|
CertificateAuthorityData: []byte(caBundle),
|
|
},
|
|
},
|
|
Contexts: map[string]*clientcmdapi.Context{
|
|
"current": {
|
|
Cluster: "cluster",
|
|
AuthInfo: "client",
|
|
},
|
|
},
|
|
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
|
"client": {},
|
|
},
|
|
}, "current", &clientcmd.ConfigOverrides{}, nil).ClientConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return versioned.NewForConfig(cfg)
|
|
}
|