mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-19 06:31:47 +00:00
Define initial config file format
- Users may want to consume pkg/config to generate configuration files. - This also involved putting config-related utilities in the config package for ease of consumption. - We did not add in versioning into the Config type for now...this is something we will likely do in the future, but it is not deemed necessary this early in the project. - The config file format tries to follow the patterns of Kube. One such example of this is requiring the use of base64-encoded CA bundle PEM bytes instead of a file path. This also slightly simplifies the config file handling because we don't have to 1) read in a file or 2) deal with the error case of the file not being there. - The webhook code from k8s.io/apiserver is really exactly what we want here. If this dependency gets too burdensome, we can always drop it, but the pros outweigh the cons at the moment. - Writing out a kubeconfig to disk to configure the webhook is a little janky, but hopefully this won't hurt performance too much in the year 2020. - Also bonus: call the right *Serve*() function when starting our servers. Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2020 VMware, Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
// Config contains knobs to setup an instance of placeholder-name.
|
||||
type Config struct {
|
||||
WebhookConfig WebhookConfigSpec `json:"webhook"`
|
||||
}
|
||||
|
||||
// WebhookConfig contains configuration knobs specific to placeholder-name's use
|
||||
// of a webhook for token validation.
|
||||
type WebhookConfigSpec struct {
|
||||
// URL contains the URL of the webhook that placeholder-name will use
|
||||
// to validate external credentials.
|
||||
URL string `json:"url"`
|
||||
|
||||
// CABundle contains PEM-encoded certificate authority certificates used
|
||||
// to validate TLS connections to the WebhookURL.
|
||||
CABundle []byte `json:"caBundle"`
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2020 VMware, Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Package config contains functionality to load/store api.Config's from/to
|
||||
// some source.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/suzerain-io/placeholder-name/pkg/config/api"
|
||||
)
|
||||
|
||||
// FromPath loads an api.Config from a provided local file path.
|
||||
//
|
||||
// Note! The api.Config file should contain base64-encoded WebhookCABundle data.
|
||||
// This function will decode that base64-encoded data to PEM bytes to be stored
|
||||
// in the api.Config.
|
||||
func FromPath(path string) (*api.Config, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read file: %w", err)
|
||||
}
|
||||
|
||||
var config api.Config
|
||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("decode yaml: %w", err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2020 VMware, Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/suzerain-io/placeholder-name/pkg/config/api"
|
||||
)
|
||||
|
||||
func TestFromPath(t *testing.T) {
|
||||
expect := require.New(t)
|
||||
|
||||
config, err := FromPath("testdata/happy.yaml")
|
||||
expect.NoError(err)
|
||||
expect.Equal(config, &api.Config{
|
||||
WebhookConfig: api.WebhookConfigSpec{
|
||||
URL: "https://tuna.com/fish?marlin",
|
||||
CABundle: []byte("-----BEGIN CERTIFICATE-----..."),
|
||||
},
|
||||
})
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
webhook:
|
||||
url: https://tuna.com/fish?marlin
|
||||
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tLi4u
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2020 VMware, Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"github.com/suzerain-io/placeholder-name/pkg/config/api"
|
||||
)
|
||||
|
||||
// NewWebhook creates a webhook from the provided API server url and caBundle
|
||||
// used to validate TLS connections.
|
||||
func NewWebhook(spec api.WebhookConfigSpec) (*webhook.WebhookTokenAuthenticator, error) {
|
||||
kubeconfig, err := ioutil.TempFile("", "placeholder-name-webhook-kubeconfig-*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create temp file: %w", err)
|
||||
}
|
||||
defer os.Remove(kubeconfig.Name())
|
||||
|
||||
if err := anonymousKubeconfig(spec.URL, spec.CABundle, kubeconfig); err != nil {
|
||||
return nil, fmt.Errorf("anonymous kubeconfig: %w", err)
|
||||
}
|
||||
|
||||
// We use v1beta1 instead of v1 since v1beta1 is more prevalent in our desired
|
||||
// integration points.
|
||||
version := authenticationv1beta1.SchemeGroupVersion.Version
|
||||
|
||||
// At the current time, we don't provide any audiences because we simply don't
|
||||
// have any requirements to do so. This can be changed in the future as
|
||||
// requirements change.
|
||||
var implicitAuds authenticator.Audiences
|
||||
|
||||
// We set this to nil because we would only need this to support some of the
|
||||
// custom proxy stuff used by the API server.
|
||||
var customDial utilnet.DialFunc
|
||||
|
||||
return webhook.New(kubeconfig.Name(), version, implicitAuds, customDial)
|
||||
}
|
||||
|
||||
// anonymousKubeconfig writes a kubeconfig file to the provided io.Writer that
|
||||
// will "use" anonymous auth to talk to a Kube API server at the provided url
|
||||
// with the provided caBundle.
|
||||
func anonymousKubeconfig(url string, caBundle []byte, out io.Writer) error {
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["anonymous-cluster"] = &clientcmdapi.Cluster{
|
||||
Server: url,
|
||||
CertificateAuthorityData: caBundle,
|
||||
}
|
||||
config.Contexts["anonymous"] = &clientcmdapi.Context{
|
||||
Cluster: "anonymous-cluster",
|
||||
}
|
||||
config.CurrentContext = "anonymous"
|
||||
|
||||
data, err := clientcmd.Write(*config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal config: %w", err)
|
||||
}
|
||||
|
||||
if _, err := out.Write(data); err != nil {
|
||||
return fmt.Errorf("write config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2020 VMware, Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
func TestAnonymousKubeconfig(t *testing.T) {
|
||||
expect := require.New(t)
|
||||
|
||||
f, err := ioutil.TempFile("", "placeholder-name-anonymous-kubeconfig-test-*")
|
||||
expect.NoError(err)
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
err = anonymousKubeconfig("https://tuna.com", []byte("ca bundle"), f)
|
||||
expect.NoError(err)
|
||||
|
||||
config, err := clientcmd.BuildConfigFromFlags("", f.Name())
|
||||
expect.NoError(err)
|
||||
|
||||
expect.Equal("https://tuna.com", config.Host)
|
||||
}
|
||||
Reference in New Issue
Block a user