Merge pull request #34 from mattmoyer/reduced-dependency-client
Reduce the dependencies of the ./pkg/client package.
This commit is contained in:
commit
b59604b47c
@ -24,6 +24,8 @@ WORKDIR /work
|
||||
# Get dependencies first so they can be cached as a layer
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
COPY pkg/client/go.mod ./pkg/client/go.mod
|
||||
COPY pkg/client/go.sum ./pkg/client/go.sum
|
||||
RUN go mod download
|
||||
# Copy only the production source code to avoid cache misses when editing other files
|
||||
COPY cmd ./cmd
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
|
||||
"github.com/suzerain-io/placeholder-name/internal/constable"
|
||||
@ -28,7 +29,7 @@ func main() {
|
||||
}
|
||||
|
||||
type envGetter func(string) (string, bool)
|
||||
type tokenExchanger func(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error)
|
||||
type tokenExchanger func(ctx context.Context, token, caBundle, apiEndpoint string) (*client.Credential, error)
|
||||
|
||||
const ErrMissingEnvVar = constable.Error("failed to login: environment variable not set")
|
||||
|
||||
@ -51,11 +52,28 @@ func run(envGetter envGetter, tokenExchanger tokenExchanger, outputWriter io.Wri
|
||||
return envVarNotSetError("PLACEHOLDER_NAME_K8S_API_ENDPOINT")
|
||||
}
|
||||
|
||||
execCredential, err := tokenExchanger(ctx, token, caBundle, apiEndpoint)
|
||||
cred, err := tokenExchanger(ctx, token, caBundle, apiEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to login: %w", err)
|
||||
}
|
||||
|
||||
var expiration *metav1.Time
|
||||
if cred.ExpirationTimestamp != nil {
|
||||
t := metav1.NewTime(*cred.ExpirationTimestamp)
|
||||
expiration = &t
|
||||
}
|
||||
execCredential := clientauthenticationv1beta1.ExecCredential{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ExecCredential",
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
},
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
|
||||
ExpirationTimestamp: expiration,
|
||||
Token: cred.Token,
|
||||
ClientCertificateData: cred.ClientCertificateData,
|
||||
ClientKeyData: cred.ClientKeyData,
|
||||
},
|
||||
}
|
||||
err = json.NewEncoder(outputWriter).Encode(execCredential)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal response to stdout: %w", err)
|
||||
|
@ -15,9 +15,8 @@ import (
|
||||
"github.com/sclevine/spec"
|
||||
"github.com/sclevine/spec/report"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
|
||||
"github.com/suzerain-io/placeholder-name/pkg/client"
|
||||
"github.com/suzerain-io/placeholder-name/test/library"
|
||||
)
|
||||
|
||||
@ -68,7 +67,7 @@ func TestRun(t *testing.T) {
|
||||
|
||||
when("the token exchange fails", func() {
|
||||
it.Before(func() {
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*client.Credential, error) {
|
||||
return nil, fmt.Errorf("some error")
|
||||
}
|
||||
})
|
||||
@ -81,10 +80,8 @@ func TestRun(t *testing.T) {
|
||||
|
||||
when("the JSON encoder fails", func() {
|
||||
it.Before(func() {
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
|
||||
return &clientauthenticationv1beta1.ExecCredential{
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{Token: "some token"},
|
||||
}, nil
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*client.Credential, error) {
|
||||
return &client.Credential{Token: "some token"}, nil
|
||||
}
|
||||
})
|
||||
|
||||
@ -96,12 +93,10 @@ func TestRun(t *testing.T) {
|
||||
|
||||
when("the token exchange times out", func() {
|
||||
it.Before(func() {
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*client.Credential, error) {
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
return &clientauthenticationv1beta1.ExecCredential{
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{Token: "some token"},
|
||||
}, nil
|
||||
return &client.Credential{Token: "some token"}, nil
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
@ -118,14 +113,14 @@ func TestRun(t *testing.T) {
|
||||
var actualToken, actualCaBundle, actualAPIEndpoint string
|
||||
|
||||
it.Before(func() {
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
|
||||
tokenExchanger = func(ctx context.Context, token, caBundle, apiEndpoint string) (*client.Credential, error) {
|
||||
actualToken, actualCaBundle, actualAPIEndpoint = token, caBundle, apiEndpoint
|
||||
return &clientauthenticationv1beta1.ExecCredential{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ExecCredential",
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
},
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{Token: "some token"},
|
||||
now := time.Date(2020, 7, 29, 1, 2, 3, 0, time.UTC)
|
||||
return &client.Credential{
|
||||
ExpirationTimestamp: &now,
|
||||
ClientCertificateData: "some certificate",
|
||||
ClientKeyData: "some key",
|
||||
Token: "some token",
|
||||
}, nil
|
||||
}
|
||||
})
|
||||
@ -141,6 +136,9 @@ func TestRun(t *testing.T) {
|
||||
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||
"spec": {},
|
||||
"status": {
|
||||
"expirationTimestamp":"2020-07-29T01:02:03Z",
|
||||
"clientCertificateData": "some certificate",
|
||||
"clientKeyData":"some key",
|
||||
"token": "some token"
|
||||
}
|
||||
}`
|
||||
|
3
go.mod
3
go.mod
@ -12,6 +12,7 @@ require (
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200724000517-dc602fd8d75e
|
||||
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200714203950-a414963b4f95
|
||||
github.com/suzerain-io/placeholder-name/pkg/client v0.0.0-00010101000000-000000000000
|
||||
golang.org/x/tools v0.0.0-20200707134715-9e0a013e855f // indirect
|
||||
k8s.io/api v0.19.0-rc.0
|
||||
k8s.io/apimachinery v0.19.0-rc.0
|
||||
@ -23,3 +24,5 @@ require (
|
||||
k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
replace github.com/suzerain-io/placeholder-name/pkg/client => ./pkg/client
|
||||
|
@ -2,3 +2,4 @@
|
||||
set -e
|
||||
|
||||
go test -race ./...
|
||||
( cd pkg/client && go test -race ./... )
|
||||
|
@ -6,77 +6,148 @@ SPDX-License-Identifier: Apache-2.0
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"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"
|
||||
|
||||
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
||||
placeholderclientset "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/clientset/versioned"
|
||||
"github.com/suzerain-io/placeholder-name/internal/constable"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrLoginFailed is returned by ExchangeToken when the server rejects the login request.
|
||||
const ErrLoginFailed = constable.Error("login failed")
|
||||
ErrLoginFailed = fmt.Errorf("login failed")
|
||||
|
||||
func ExchangeToken(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
|
||||
clientset, err := getClient(apiEndpoint, caBundle)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get API client: %w", err)
|
||||
}
|
||||
// ErrInvalidAPIEndpoint is returned by ExchangeToken when the provided API endpoint is invalid.
|
||||
ErrInvalidAPIEndpoint = fmt.Errorf("invalid API endpoint")
|
||||
|
||||
resp, err := clientset.PlaceholderV1alpha1().LoginRequests().Create(ctx, &placeholderv1alpha1.LoginRequest{
|
||||
Spec: placeholderv1alpha1.LoginRequestSpec{
|
||||
Type: placeholderv1alpha1.TokenLoginCredentialType,
|
||||
Token: &placeholderv1alpha1.LoginRequestTokenCredential{
|
||||
Value: token,
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not login: %w", err)
|
||||
}
|
||||
if resp.Status.Credential == nil || resp.Status.Message != "" {
|
||||
return nil, fmt.Errorf("%w: %s", ErrLoginFailed, resp.Status.Message)
|
||||
}
|
||||
// ErrInvalidCABundle is returned by ExchangeToken when the provided CA bundle is invalid.
|
||||
ErrInvalidCABundle = fmt.Errorf("invalid CA bundle")
|
||||
)
|
||||
|
||||
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,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
const (
|
||||
// loginRequestsAPIPath is the API path for the v1alpha1 LoginRequest API.
|
||||
loginRequestsAPIPath = "/apis/placeholder.suzerain-io.github.io/v1alpha1/loginrequests"
|
||||
|
||||
// getClient returns an anonymous clientset for the placeholder-name API at the provided endpoint/CA bundle.
|
||||
func getClient(apiEndpoint string, caBundle string) (placeholderclientset.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", nil, nil).ClientConfig()
|
||||
// userAgent is the user agent header value sent with requests.
|
||||
userAgent = "placeholder-name"
|
||||
)
|
||||
|
||||
func loginRequest(ctx context.Context, apiEndpoint *url.URL, token string) (*http.Request, error) {
|
||||
type LoginRequestTokenCredential struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
type LoginRequestSpec struct {
|
||||
Type string `json:"type"`
|
||||
Token *LoginRequestTokenCredential `json:"token"`
|
||||
}
|
||||
body := struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
Metadata struct {
|
||||
CreationTimestamp *string `json:"creationTimestamp"`
|
||||
} `json:"metadata"`
|
||||
Spec LoginRequestSpec `json:"spec"`
|
||||
Status struct{} `json:"status"`
|
||||
}{
|
||||
APIVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
||||
Kind: "LoginRequest",
|
||||
Spec: LoginRequestSpec{Type: "token", Token: &LoginRequestTokenCredential{Value: token}},
|
||||
}
|
||||
bodyJSON, err := json.Marshal(&body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return placeholderclientset.NewForConfig(cfg)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiEndpoint.String(), bytes.NewReader(bodyJSON))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Credential is the output of an ExchangeToken operation. It is equivalent to the data
|
||||
// in the Kubernetes client.authentication.k8s.io/v1beta1 ExecCredentialStatus type.
|
||||
type Credential struct {
|
||||
// ExpirationTimestamp indicates a time when the provided credentials expire.
|
||||
ExpirationTimestamp *time.Time
|
||||
|
||||
// Token is a bearer token used by the client for request authentication.
|
||||
Token string
|
||||
|
||||
// PEM-encoded client TLS certificates (including intermediates, if any).
|
||||
ClientCertificateData string
|
||||
|
||||
// PEM-encoded private key for the above certificate.
|
||||
ClientKeyData string
|
||||
}
|
||||
|
||||
func ExchangeToken(ctx context.Context, token, caBundle, apiEndpoint string) (*Credential, error) {
|
||||
// Parse and validate the provided API endpoint.
|
||||
endpointURL, err := url.Parse(apiEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrInvalidAPIEndpoint, err.Error())
|
||||
}
|
||||
if endpointURL.Scheme != "https" {
|
||||
return nil, fmt.Errorf(`%w: protocol must be "https", not %q`, ErrInvalidAPIEndpoint, endpointURL.Scheme)
|
||||
}
|
||||
|
||||
// Form the LoginRequest API URL by appending the API path to the main API endpoint.
|
||||
placeholderEndpointURL := *endpointURL
|
||||
placeholderEndpointURL.Path = filepath.Join(placeholderEndpointURL.Path, loginRequestsAPIPath)
|
||||
|
||||
// Initialize a TLS client configuration from the provided CA bundle.
|
||||
tlsConfig := tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: x509.NewCertPool(),
|
||||
}
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(caBundle)) {
|
||||
return nil, fmt.Errorf("%w: no certificates found", ErrInvalidCABundle)
|
||||
}
|
||||
|
||||
// Create a request object for the "POST /apis/placeholder.suzerain-io.github.io/v1alpha1/loginrequests" request.
|
||||
req, err := loginRequest(ctx, &placeholderEndpointURL, token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not build request: %w", err)
|
||||
}
|
||||
|
||||
client := http.Client{Transport: &http.Transport{TLSClientConfig: &tlsConfig}}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not login: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("could not login: server returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var respBody struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
Status struct {
|
||||
Credential *struct {
|
||||
ClientCertificateData string `json:"clientCertificateData"`
|
||||
ClientKeyData string `json:"clientKeyData"`
|
||||
}
|
||||
Message string `json:"message"`
|
||||
} `json:"status"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||
return nil, fmt.Errorf("invalid login response: %w", err)
|
||||
}
|
||||
|
||||
if respBody.Status.Credential == nil || respBody.Status.Message != "" {
|
||||
return nil, fmt.Errorf("%w: %s", ErrLoginFailed, respBody.Status.Message)
|
||||
}
|
||||
|
||||
return &Credential{
|
||||
ClientCertificateData: respBody.Status.Credential.ClientCertificateData,
|
||||
ClientKeyData: respBody.Status.Credential.ClientKeyData,
|
||||
}, nil
|
||||
}
|
||||
|
@ -7,18 +7,14 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
|
||||
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
||||
)
|
||||
|
||||
func startTestServer(t *testing.T, handler http.HandlerFunc) (string, string) {
|
||||
@ -39,8 +35,45 @@ func TestExchangeToken(t *testing.T) {
|
||||
|
||||
t.Run("invalid configuration", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := ExchangeToken(ctx, "", "", "")
|
||||
require.EqualError(t, err, "could not get API client: invalid configuration: no configuration has been provided, try setting KUBERNETES_MASTER environment variable")
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
caBundle string
|
||||
apiEndpoint string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "bad URL",
|
||||
apiEndpoint: "%@Q$!",
|
||||
wantErr: `invalid API endpoint: parse "%@Q$!": invalid URL escape "%@Q"`,
|
||||
},
|
||||
{
|
||||
name: "plain HTTP URL",
|
||||
apiEndpoint: "http://example.com",
|
||||
wantErr: `invalid API endpoint: protocol must be "https", not "http"`,
|
||||
},
|
||||
{
|
||||
name: "no CA certs",
|
||||
apiEndpoint: "https://example.com",
|
||||
caBundle: "",
|
||||
wantErr: `invalid CA bundle: no certificates found`,
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ExchangeToken(ctx, "", tt.caBundle, tt.apiEndpoint)
|
||||
require.EqualError(t, err, tt.wantErr)
|
||||
require.Nil(t, got)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("request creation failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Start a test server that doesn't do anything.
|
||||
caBundle, endpoint := startTestServer(t, func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
got, err := ExchangeToken(nil, "", caBundle, endpoint)
|
||||
require.EqualError(t, err, `could not build request: net/http: nil Context`)
|
||||
require.Nil(t, got)
|
||||
})
|
||||
|
||||
@ -53,7 +86,43 @@ func TestExchangeToken(t *testing.T) {
|
||||
})
|
||||
|
||||
got, err := ExchangeToken(ctx, "", caBundle, endpoint)
|
||||
require.EqualError(t, err, `could not login: an error on the server ("some server error") has prevented the request from succeeding (post loginrequests.placeholder.suzerain-io.github.io)`)
|
||||
require.EqualError(t, err, `could not login: server returned status 500`)
|
||||
require.Nil(t, got)
|
||||
})
|
||||
|
||||
t.Run("request failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
clientTimeout := 500 * time.Millisecond
|
||||
|
||||
// Start a test server that is slow to respond.
|
||||
caBundle, endpoint := startTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
time.Sleep(2 * clientTimeout)
|
||||
_, _ = w.Write([]byte("slow response"))
|
||||
})
|
||||
|
||||
// Make a request using short timeout.
|
||||
ctx, cancel := context.WithTimeout(ctx, clientTimeout)
|
||||
defer cancel()
|
||||
|
||||
got, err := ExchangeToken(ctx, "", caBundle, endpoint)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "context deadline exceeded")
|
||||
require.Contains(t, err.Error(), "could not login:")
|
||||
require.Nil(t, got)
|
||||
})
|
||||
|
||||
t.Run("server invalid JSON", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Start a test server that returns only 500 errors.
|
||||
caBundle, endpoint := startTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = w.Write([]byte("not valid json"))
|
||||
})
|
||||
|
||||
got, err := ExchangeToken(ctx, "", caBundle, endpoint)
|
||||
require.EqualError(t, err, `invalid login response: invalid character 'o' in literal null (expecting 'u')`)
|
||||
require.Nil(t, got)
|
||||
})
|
||||
|
||||
@ -62,10 +131,19 @@ func TestExchangeToken(t *testing.T) {
|
||||
// Start a test server that returns success but with an error message
|
||||
caBundle, endpoint := startTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("content-type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(&placeholderv1alpha1.LoginRequest{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "placeholder.suzerain-io.github.io/v1alpha1", Kind: "LoginRequest"},
|
||||
Status: placeholderv1alpha1.LoginRequestStatus{Message: "some login failure"},
|
||||
})
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = w.Write([]byte(`
|
||||
{
|
||||
"kind": "LoginRequest",
|
||||
"apiVersion": "placeholder.suzerain-io.github.io/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"spec": {},
|
||||
"status": {
|
||||
"message": "some login failure"
|
||||
}
|
||||
}`))
|
||||
})
|
||||
|
||||
got, err := ExchangeToken(ctx, "", caBundle, endpoint)
|
||||
@ -78,6 +156,8 @@ func TestExchangeToken(t *testing.T) {
|
||||
|
||||
// Start a test server that returns successfully and asserts various properties of the request.
|
||||
caBundle, endpoint := startTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, http.MethodPost, r.Method)
|
||||
require.Equal(t, "/apis/placeholder.suzerain-io.github.io/v1alpha1/loginrequests", r.URL.Path)
|
||||
require.Equal(t, "application/json", r.Header.Get("content-type"))
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
@ -91,7 +171,9 @@ func TestExchangeToken(t *testing.T) {
|
||||
},
|
||||
"spec": {
|
||||
"type": "token",
|
||||
"token": {}
|
||||
"token": {
|
||||
"value": "test-token"
|
||||
}
|
||||
},
|
||||
"status": {}
|
||||
}`,
|
||||
@ -99,28 +181,29 @@ func TestExchangeToken(t *testing.T) {
|
||||
)
|
||||
|
||||
w.Header().Set("content-type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(&placeholderv1alpha1.LoginRequest{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "placeholder.suzerain-io.github.io/v1alpha1", Kind: "LoginRequest"},
|
||||
Status: placeholderv1alpha1.LoginRequestStatus{
|
||||
Credential: &placeholderv1alpha1.LoginRequestCredential{
|
||||
ClientCertificateData: "test-certificate",
|
||||
ClientKeyData: "test-key",
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = w.Write([]byte(`
|
||||
{
|
||||
"kind": "LoginRequest",
|
||||
"apiVersion": "placeholder.suzerain-io.github.io/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
},
|
||||
})
|
||||
"spec": {},
|
||||
"status": {
|
||||
"credential": {
|
||||
"clientCertificateData": "test-certificate",
|
||||
"clientKeyData": "test-key"
|
||||
}
|
||||
}
|
||||
}`))
|
||||
})
|
||||
|
||||
got, err := ExchangeToken(ctx, "", caBundle, endpoint)
|
||||
got, err := ExchangeToken(ctx, "test-token", caBundle, endpoint)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &clientauthenticationv1beta1.ExecCredential{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ExecCredential",
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
},
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
|
||||
require.Equal(t, &Credential{
|
||||
ClientCertificateData: "test-certificate",
|
||||
ClientKeyData: "test-key",
|
||||
},
|
||||
}, got)
|
||||
})
|
||||
}
|
||||
|
5
pkg/client/go.mod
Normal file
5
pkg/client/go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/suzerain-io/placeholder-name/pkg/client
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/stretchr/testify v1.6.1
|
11
pkg/client/go.sum
Normal file
11
pkg/client/go.sum
Normal file
@ -0,0 +1,11 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
@ -69,7 +69,7 @@ func TestClient(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a client using the certificate and key returned by the token exchange.
|
||||
validClient := library.NewClientsetWithConfig(t, library.NewClientConfigWithCertAndKey(t, resp.Status.ClientCertificateData, resp.Status.ClientKeyData))
|
||||
validClient := library.NewClientsetWithConfig(t, library.NewClientConfigWithCertAndKey(t, resp.ClientCertificateData, resp.ClientKeyData))
|
||||
|
||||
// Make a version request, which should succeed even without any authorization.
|
||||
_, err = validClient.Discovery().ServerVersion()
|
||||
|
Loading…
Reference in New Issue
Block a user