mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-19 06:31:47 +00:00
Use custom suffix in Spec.Authenticator.APIGroup of TokenCredentialRequest
When the Pinniped server has been installed with the `api_group_suffix` option, for example using `mysuffix.com`, then clients who would like to submit a `TokenCredentialRequest` to the server should set the `Spec.Authenticator.APIGroup` field as `authentication.concierge.mysuffix.com`. This makes more sense from the client's point of view than using the default `authentication.concierge.pinniped.dev` because `authentication.concierge.mysuffix.com` is the name of the API group that they can observe their cluster and `authentication.concierge.pinniped.dev` does not exist as an API group on their cluster. This commit includes both the client and server-side changes to make this work, as well as integration test updates. Co-authored-by: Andrew Keesler <akeesler@vmware.com> Co-authored-by: Ryan Richard <richardry@vmware.com> Co-authored-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
co-authored by
Andrew Keesler
Margo Crawford
parent
26922307ad
commit
288d9c999e
@@ -12,7 +12,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "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"
|
||||
@@ -34,11 +34,12 @@ type Option func(*Client) error
|
||||
|
||||
// Client is a configuration for talking to the Pinniped concierge.
|
||||
type Client struct {
|
||||
namespace string
|
||||
authenticator *corev1.TypedLocalObjectReference
|
||||
caBundle string
|
||||
endpoint *url.URL
|
||||
apiGroupSuffix string
|
||||
namespace string
|
||||
authenticatorName string
|
||||
authenticatorKind string
|
||||
caBundle string
|
||||
endpoint *url.URL
|
||||
apiGroupSuffix string
|
||||
}
|
||||
|
||||
// WithNamespace configures the namespace where the TokenCredentialRequest is to be sent.
|
||||
@@ -55,18 +56,15 @@ func WithAuthenticator(authType, authName string) Option {
|
||||
if authName == "" {
|
||||
return fmt.Errorf("authenticator name must not be empty")
|
||||
}
|
||||
authenticator := corev1.TypedLocalObjectReference{Name: authName}
|
||||
c.authenticatorName = authName
|
||||
switch strings.ToLower(authType) {
|
||||
case "webhook":
|
||||
authenticator.APIGroup = &auth1alpha1.SchemeGroupVersion.Group
|
||||
authenticator.Kind = "WebhookAuthenticator"
|
||||
c.authenticatorKind = "WebhookAuthenticator"
|
||||
case "jwt":
|
||||
authenticator.APIGroup = &auth1alpha1.SchemeGroupVersion.Group
|
||||
authenticator.Kind = "JWTAuthenticator"
|
||||
c.authenticatorKind = "JWTAuthenticator"
|
||||
default:
|
||||
return fmt.Errorf(`invalid authenticator type: %q, supported values are "webhook" and "jwt"`, authType)
|
||||
}
|
||||
c.authenticator = &authenticator
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -133,7 +131,7 @@ func New(opts ...Option) (*Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if c.authenticator == nil {
|
||||
if c.authenticatorName == "" {
|
||||
return nil, fmt.Errorf("WithAuthenticator must be specified")
|
||||
}
|
||||
if c.endpoint == nil {
|
||||
@@ -180,13 +178,18 @@ func (c *Client) ExchangeToken(ctx context.Context, token string) (*clientauthen
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
replacedAPIGroupName, _ := groupsuffix.Replace(auth1alpha1.SchemeGroupVersion.Group, c.apiGroupSuffix)
|
||||
resp, err := clientset.LoginV1alpha1().TokenCredentialRequests(c.namespace).Create(ctx, &loginv1alpha1.TokenCredentialRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: c.namespace,
|
||||
},
|
||||
Spec: loginv1alpha1.TokenCredentialRequestSpec{
|
||||
Token: token,
|
||||
Authenticator: *c.authenticator,
|
||||
Token: token,
|
||||
Authenticator: v1.TypedLocalObjectReference{
|
||||
APIGroup: &replacedAPIGroupName,
|
||||
Kind: c.authenticatorKind,
|
||||
Name: c.authenticatorName,
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
loginv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1"
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
)
|
||||
|
||||
@@ -220,47 +221,7 @@ func TestExchangeToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
expires := metav1.NewTime(time.Now().Truncate(time.Second))
|
||||
|
||||
// Start a test server that returns successfully and asserts various properties of the request.
|
||||
caBundle, endpoint := testutil.TLSTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, http.MethodPost, r.Method)
|
||||
require.Equal(t, "/apis/login.concierge.pinniped.dev/v1alpha1/namespaces/test-namespace/tokencredentialrequests", r.URL.Path)
|
||||
require.Equal(t, "application/json", r.Header.Get("content-type"))
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t,
|
||||
`{
|
||||
"kind": "TokenCredentialRequest",
|
||||
"apiVersion": "login.concierge.pinniped.dev/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"namespace": "test-namespace"
|
||||
},
|
||||
"spec": {
|
||||
"token": "test-token",
|
||||
"authenticator": {
|
||||
"apiGroup": "authentication.concierge.pinniped.dev",
|
||||
"kind": "WebhookAuthenticator",
|
||||
"name": "test-webhook"
|
||||
}
|
||||
},
|
||||
"status": {}
|
||||
}`,
|
||||
string(body),
|
||||
)
|
||||
|
||||
w.Header().Set("content-type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(&loginv1alpha1.TokenCredentialRequest{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "login.concierge.pinniped.dev/v1alpha1", Kind: "TokenCredentialRequest"},
|
||||
Status: loginv1alpha1.TokenCredentialRequestStatus{
|
||||
Credential: &loginv1alpha1.ClusterCredential{
|
||||
ExpirationTimestamp: expires,
|
||||
ClientCertificateData: "test-certificate",
|
||||
ClientKeyData: "test-key",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
caBundle, endpoint := runFakeServer(t, expires, "pinniped.dev")
|
||||
|
||||
client, err := New(WithNamespace("test-namespace"), WithEndpoint(endpoint), WithCABundle(caBundle), WithAuthenticator("webhook", "test-webhook"))
|
||||
require.NoError(t, err)
|
||||
@@ -279,4 +240,78 @@ func TestExchangeToken(t *testing.T) {
|
||||
},
|
||||
}, got)
|
||||
})
|
||||
|
||||
t.Run("changing the API group suffix for the client sends the custom suffix on the CredentialRequest's APIGroup and on its spec.Authenticator.APIGroup", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
expires := metav1.NewTime(time.Now().Truncate(time.Second))
|
||||
|
||||
caBundle, endpoint := runFakeServer(t, expires, "suffix.com")
|
||||
|
||||
client, err := New(WithAPIGroupSuffix("suffix.com"), WithNamespace("test-namespace"), WithEndpoint(endpoint), WithCABundle(caBundle), WithAuthenticator("webhook", "test-webhook"))
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := client.ExchangeToken(ctx, "test-token")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &clientauthenticationv1beta1.ExecCredential{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ExecCredential",
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
},
|
||||
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
|
||||
ClientCertificateData: "test-certificate",
|
||||
ClientKeyData: "test-key",
|
||||
ExpirationTimestamp: &expires,
|
||||
},
|
||||
}, got)
|
||||
})
|
||||
}
|
||||
|
||||
// Start a test server that returns successfully and asserts various properties of the request.
|
||||
func runFakeServer(t *testing.T, expires metav1.Time, pinnipedAPIGroupSuffix string) (string, string) {
|
||||
caBundle, endpoint := testutil.TLSTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, http.MethodPost, r.Method)
|
||||
require.Equal(t,
|
||||
fmt.Sprintf("/apis/login.concierge.%s/v1alpha1/namespaces/test-namespace/tokencredentialrequests", pinnipedAPIGroupSuffix),
|
||||
r.URL.Path)
|
||||
require.Equal(t, "application/json", r.Header.Get("content-type"))
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, here.Docf(
|
||||
`{
|
||||
"kind": "TokenCredentialRequest",
|
||||
"apiVersion": "login.concierge.%s/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"namespace": "test-namespace"
|
||||
},
|
||||
"spec": {
|
||||
"token": "test-token",
|
||||
"authenticator": {
|
||||
"apiGroup": "authentication.concierge.%s",
|
||||
"kind": "WebhookAuthenticator",
|
||||
"name": "test-webhook"
|
||||
}
|
||||
},
|
||||
"status": {}
|
||||
}`, pinnipedAPIGroupSuffix, pinnipedAPIGroupSuffix),
|
||||
string(body),
|
||||
)
|
||||
|
||||
w.Header().Set("content-type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(&loginv1alpha1.TokenCredentialRequest{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: fmt.Sprintf("login.concierge.%s/v1alpha1", pinnipedAPIGroupSuffix),
|
||||
Kind: "TokenCredentialRequest",
|
||||
},
|
||||
Status: loginv1alpha1.TokenCredentialRequestStatus{
|
||||
Credential: &loginv1alpha1.ClusterCredential{
|
||||
ExpirationTimestamp: expires,
|
||||
ClientCertificateData: "test-certificate",
|
||||
ClientKeyData: "test-key",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
return caBundle, endpoint
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user