2020-09-16 14:19:51 +00:00
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
2020-08-25 15:48:14 +00:00
// Package client is a wrapper for interacting with Pinniped's CredentialRequest API.
package client
import (
"context"
"errors"
"fmt"
2020-09-17 22:11:47 +00:00
corev1 "k8s.io/api/core/v1"
2020-08-25 15:48:14 +00:00
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"
2020-09-18 19:56:24 +00:00
"go.pinniped.dev/generated/1.19/apis/login/v1alpha1"
"go.pinniped.dev/generated/1.19/client/clientset/versioned"
2020-08-25 15:48:14 +00:00
)
// ErrLoginFailed is returned by ExchangeToken when the server rejects the login request.
var ErrLoginFailed = errors . New ( "login failed" )
2020-09-17 22:11:47 +00:00
// 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 , idp corev1 . TypedLocalObjectReference , token string , caBundle string , apiEndpoint string ) ( * clientauthenticationv1beta1 . ExecCredential , error ) {
2020-08-25 15:48:14 +00:00
client , err := getClient ( apiEndpoint , caBundle )
if err != nil {
return nil , fmt . Errorf ( "could not get API client: %w" , err )
}
2020-09-16 20:03:54 +00:00
resp , err := client . LoginV1alpha1 ( ) . TokenCredentialRequests ( namespace ) . Create ( ctx , & v1alpha1 . TokenCredentialRequest {
2020-09-21 22:31:07 +00:00
ObjectMeta : metav1 . ObjectMeta {
Namespace : namespace ,
} ,
2020-09-17 22:11:47 +00:00
Spec : v1alpha1 . TokenCredentialRequestSpec {
Token : token ,
IdentityProvider : idp ,
} ,
2020-08-25 15:48:14 +00:00
} , metav1 . CreateOptions { } )
if err != nil {
return nil , fmt . Errorf ( "could not login: %w" , err )
}
if resp . Status . Credential == nil || resp . Status . Message != nil {
2020-09-16 20:03:54 +00:00
if resp . Status . Message != nil {
return nil , fmt . Errorf ( "%w: %s" , ErrLoginFailed , * resp . Status . Message )
}
return nil , fmt . Errorf ( "%w: unknown" , ErrLoginFailed )
2020-08-25 15:48:14 +00:00
}
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 )
}