ContainerImage.Pinniped/internal/client/client.go

86 lines
2.9 KiB
Go
Raw Normal View History

/*
Copyright 2020 VMware, Inc.
SPDX-License-Identifier: Apache-2.0
*/
// Package client is a wrapper for interacting with Pinniped's CredentialRequest API.
package client
import (
"context"
"errors"
"fmt"
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"
"github.com/suzerain-io/pinniped/generated/1.19/apis/pinniped/v1alpha1"
"github.com/suzerain-io/pinniped/generated/1.19/client/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 CredentialRequest API, returning a client-go ExecCredential valid on the target cluster.
func ExchangeToken(ctx context.Context, 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.PinnipedV1alpha1().CredentialRequests().Create(ctx, &v1alpha1.CredentialRequest{
Spec: v1alpha1.CredentialRequestSpec{
Type: v1alpha1.TokenCredentialType,
Token: &v1alpha1.CredentialRequestTokenCredential{
Value: token,
},
},
}, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("could not login: %w", err)
}
if resp.Status.Credential == nil || resp.Status.Message != nil {
return nil, fmt.Errorf("%w: %s", ErrLoginFailed, *resp.Status.Message)
}
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)
}