mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 10:53:34 +00:00
vendor files
This commit is contained in:
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/imagepolicy/install:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/cache:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/webhook:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"certs_test.go",
|
||||
"config_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/imagepolicy/install:go_default_library",
|
||||
"//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
251
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
251
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/imagepolicy/v1alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
// install the clientgo image policy API for use with api registry
|
||||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
)
|
||||
|
||||
var (
|
||||
groupVersions = []schema.GroupVersion{v1alpha1.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("ImagePolicyWebhook", func(config io.Reader) (admission.Interface, error) {
|
||||
newImagePolicyWebhook, err := NewImagePolicyWebhook(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newImagePolicyWebhook, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
webhook *webhook.GenericWebhook
|
||||
responseCache *cache.LRUExpireCache
|
||||
allowTTL time.Duration
|
||||
denyTTL time.Duration
|
||||
retryBackoff time.Duration
|
||||
defaultAllow bool
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
func (a *Plugin) statusTTL(status v1alpha1.ImageReviewStatus) time.Duration {
|
||||
if status.Allowed {
|
||||
return a.allowTTL
|
||||
}
|
||||
return a.denyTTL
|
||||
}
|
||||
|
||||
// Filter out annotations that don't match *.image-policy.k8s.io/*
|
||||
func (a *Plugin) filterAnnotations(allAnnotations map[string]string) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range allAnnotations {
|
||||
if strings.Contains(k, ".image-policy.k8s.io/") {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// Function to call on webhook failure; behavior determined by defaultAllow flag
|
||||
func (a *Plugin) webhookError(pod *api.Pod, attributes admission.Attributes, err error) error {
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error contacting webhook backend: %s", err)
|
||||
if a.defaultAllow {
|
||||
annotations := pod.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations[api.ImagePolicyFailedOpenKey] = "true"
|
||||
pod.ObjectMeta.SetAnnotations(annotations)
|
||||
glog.V(2).Infof("resource allowed in spite of webhook backend failure")
|
||||
return nil
|
||||
}
|
||||
glog.V(2).Infof("resource not allowed due to webhook backend failure ")
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes
|
||||
func (a *Plugin) Validate(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if attributes.GetSubresource() != "" || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
// Build list of ImageReviewContainerSpec
|
||||
var imageReviewContainerSpecs []v1alpha1.ImageReviewContainerSpec
|
||||
containers := make([]api.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
|
||||
containers = append(containers, pod.Spec.Containers...)
|
||||
containers = append(containers, pod.Spec.InitContainers...)
|
||||
for _, c := range containers {
|
||||
imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{
|
||||
Image: c.Image,
|
||||
})
|
||||
}
|
||||
imageReview := v1alpha1.ImageReview{
|
||||
Spec: v1alpha1.ImageReviewSpec{
|
||||
Containers: imageReviewContainerSpecs,
|
||||
Annotations: a.filterAnnotations(pod.Annotations),
|
||||
Namespace: attributes.GetNamespace(),
|
||||
},
|
||||
}
|
||||
if err := a.admitPod(pod, attributes, &imageReview); err != nil {
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Plugin) admitPod(pod *api.Pod, attributes admission.Attributes, review *v1alpha1.ImageReview) error {
|
||||
cacheKey, err := json.Marshal(review.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry, ok := a.responseCache.Get(string(cacheKey)); ok {
|
||||
review.Status = entry.(v1alpha1.ImageReviewStatus)
|
||||
} else {
|
||||
result := a.webhook.WithExponentialBackoff(func() rest.Result {
|
||||
return a.webhook.RestClient.Post().Body(review).Do()
|
||||
})
|
||||
|
||||
if err := result.Error(); err != nil {
|
||||
return a.webhookError(pod, attributes, err)
|
||||
}
|
||||
var statusCode int
|
||||
if result.StatusCode(&statusCode); statusCode < 200 || statusCode >= 300 {
|
||||
return a.webhookError(pod, attributes, fmt.Errorf("Error contacting webhook: %d", statusCode))
|
||||
}
|
||||
|
||||
if err := result.Into(review); err != nil {
|
||||
return a.webhookError(pod, attributes, err)
|
||||
}
|
||||
|
||||
a.responseCache.Add(string(cacheKey), review.Status, a.statusTTL(review.Status))
|
||||
}
|
||||
|
||||
if !review.Status.Allowed {
|
||||
if len(review.Status.Reason) > 0 {
|
||||
return fmt.Errorf("image policy webhook backend denied one or more images: %s", review.Status.Reason)
|
||||
}
|
||||
return errors.New("one or more images rejected by webhook backend")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewImagePolicyWebhook a new ImagePolicyWebhook plugin from the provided config file.
|
||||
// The config file is specified by --admission-control-config-file and has the
|
||||
// following format for a webhook:
|
||||
//
|
||||
// {
|
||||
// "imagePolicy": {
|
||||
// "kubeConfigFile": "path/to/kubeconfig/for/backend",
|
||||
// "allowTTL": 30, # time in s to cache approval
|
||||
// "denyTTL": 30, # time in s to cache denial
|
||||
// "retryBackoff": 500, # time in ms to wait between retries
|
||||
// "defaultAllow": true # determines behavior if the webhook backend fails
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The config file may be json or yaml.
|
||||
//
|
||||
// The kubeconfig property refers to another file in the kubeconfig format which
|
||||
// specifies how to connect to the webhook backend.
|
||||
//
|
||||
// The kubeconfig's cluster field is used to refer to the remote service, user refers to the returned authorizer.
|
||||
//
|
||||
// # clusters refers to the remote service.
|
||||
// clusters:
|
||||
// - name: name-of-remote-imagepolicy-service
|
||||
// cluster:
|
||||
// certificate-authority: /path/to/ca.pem # CA for verifying the remote service.
|
||||
// server: https://images.example.com/policy # URL of remote service to query. Must use 'https'.
|
||||
//
|
||||
// # users refers to the API server's webhook configuration.
|
||||
// users:
|
||||
// - name: name-of-api-server
|
||||
// user:
|
||||
// client-certificate: /path/to/cert.pem # cert for the webhook plugin to use
|
||||
// client-key: /path/to/key.pem # key matching the cert
|
||||
//
|
||||
// For additional HTTP configuration, refer to the kubeconfig documentation
|
||||
// http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html.
|
||||
func NewImagePolicyWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
if configFile == nil {
|
||||
return nil, fmt.Errorf("no config specified")
|
||||
}
|
||||
|
||||
// TODO: move this to a versioned configuration file format
|
||||
var config AdmissionConfig
|
||||
d := yaml.NewYAMLOrJSONDecoder(configFile, 4096)
|
||||
err := d.Decode(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whConfig := config.ImagePolicyWebhook
|
||||
if err := normalizeWebhookConfig(&whConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gw, err := webhook.NewGenericWebhook(legacyscheme.Registry, legacyscheme.Codecs, whConfig.KubeConfigFile, groupVersions, whConfig.RetryBackoff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
webhook: gw,
|
||||
responseCache: cache.NewLRUExpireCache(1024),
|
||||
allowTTL: whConfig.AllowTTL,
|
||||
denyTTL: whConfig.DenyTTL,
|
||||
defaultAllow: whConfig.DefaultAllow,
|
||||
}, nil
|
||||
}
|
948
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
948
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,948 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/imagepolicy/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
)
|
||||
|
||||
const defaultConfigTmplJSON = `
|
||||
{
|
||||
"imagePolicy": {
|
||||
"kubeConfigFile": "{{ .KubeConfig }}",
|
||||
"allowTTL": {{ .AllowTTL }},
|
||||
"denyTTL": {{ .DenyTTL }},
|
||||
"retryBackoff": {{ .RetryBackoff }},
|
||||
"defaultAllow": {{ .DefaultAllow }}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const defaultConfigTmplYAML = `
|
||||
imagePolicy:
|
||||
kubeConfigFile: "{{ .KubeConfig }}"
|
||||
allowTTL: {{ .AllowTTL }}
|
||||
denyTTL: {{ .DenyTTL }}
|
||||
retryBackoff: {{ .RetryBackoff }}
|
||||
defaultAllow: {{ .DefaultAllow }}
|
||||
`
|
||||
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
data := struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
}{
|
||||
CA: filepath.Join(dir, "ca.pem"),
|
||||
Cert: filepath.Join(dir, "clientcert.pem"),
|
||||
Key: filepath.Join(dir, "clientkey.pem"),
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{data.CA, caCert},
|
||||
{data.Cert, clientCert},
|
||||
{data.Key, clientKey},
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := ioutil.WriteFile(file.name, file.data, 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
kubeConfigTmpl string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
msg: "a single cluster and single user",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
users:
|
||||
- name: a cluster
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with no context",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with a context",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: barfoo
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
msg: "cluster with bad certificate path specified",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: foobar
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
err := func() error {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
|
||||
tmpl, err := template.New("test").Parse(tt.kubeConfigTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
if err := tmpl.Execute(tempfile, data); err != nil {
|
||||
return fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
tempconfigfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pc := tempconfigfile.Name()
|
||||
defer os.Remove(pc)
|
||||
|
||||
configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
dataConfig := struct {
|
||||
KubeConfig string
|
||||
AllowTTL int
|
||||
DenyTTL int
|
||||
RetryBackoff int
|
||||
DefaultAllow bool
|
||||
}{
|
||||
KubeConfig: p,
|
||||
AllowTTL: 500,
|
||||
DenyTTL: 500,
|
||||
RetryBackoff: 500,
|
||||
DefaultAllow: true,
|
||||
}
|
||||
if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
|
||||
return fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
// Create a new admission controller
|
||||
configFile, err := os.Open(pc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read test config: %v", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
|
||||
_, err = NewImagePolicyWebhook(configFile)
|
||||
return err
|
||||
}()
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("failed to load plugin from config %q: %v", tt.msg, err)
|
||||
}
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("wanted an error when loading config, did not get one: %q", tt.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Service mocks a remote service.
|
||||
type Service interface {
|
||||
Review(*v1alpha1.ImageReview)
|
||||
HTTPStatusCode() int
|
||||
}
|
||||
|
||||
// NewTestServer wraps a Service as an httptest.Server.
|
||||
func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if cert != nil {
|
||||
cert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
if caCert != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.ClientCAs = rootCAs
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||
var review v1alpha1.ImageReview
|
||||
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if s.HTTPStatusCode() < 200 || s.HTTPStatusCode() >= 300 {
|
||||
http.Error(w, "HTTP Error", s.HTTPStatusCode())
|
||||
return
|
||||
}
|
||||
s.Review(&review)
|
||||
type status struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
resp := struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
Status status `json:"status"`
|
||||
}{
|
||||
APIVersion: v1alpha1.SchemeGroupVersion.String(),
|
||||
Kind: "ImageReview",
|
||||
Status: status{review.Status.Allowed, review.Status.Reason},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||
server.TLS = tlsConfig
|
||||
server.StartTLS()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// A service that can be set to allow all or deny all authorization requests.
|
||||
type mockService struct {
|
||||
allow bool
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (m *mockService) Review(r *v1alpha1.ImageReview) {
|
||||
r.Status.Allowed = m.allow
|
||||
|
||||
// hardcoded overrides
|
||||
if r.Spec.Containers[0].Image == "good" {
|
||||
r.Status.Allowed = true
|
||||
}
|
||||
|
||||
for _, c := range r.Spec.Containers {
|
||||
if c.Image == "bad" {
|
||||
r.Status.Allowed = false
|
||||
}
|
||||
}
|
||||
|
||||
if !r.Status.Allowed {
|
||||
r.Status.Reason = "not allowed"
|
||||
}
|
||||
}
|
||||
func (m *mockService) Allow() { m.allow = true }
|
||||
func (m *mockService) Deny() { m.allow = false }
|
||||
func (m *mockService) HTTPStatusCode() int { return m.statusCode }
|
||||
|
||||
// newImagePolicyWebhook creates a temporary kubeconfig file from the provided arguments and attempts to load
|
||||
// a new newImagePolicyWebhook from it.
|
||||
func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{
|
||||
Cluster: v1.Cluster{Server: callbackURL, CertificateAuthorityData: ca},
|
||||
},
|
||||
},
|
||||
AuthInfos: []v1.NamedAuthInfo{
|
||||
{
|
||||
AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempconfigfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc := tempconfigfile.Name()
|
||||
defer os.Remove(pc)
|
||||
|
||||
configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplYAML)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
dataConfig := struct {
|
||||
KubeConfig string
|
||||
AllowTTL int64
|
||||
DenyTTL int64
|
||||
RetryBackoff int64
|
||||
DefaultAllow bool
|
||||
}{
|
||||
KubeConfig: p,
|
||||
AllowTTL: cacheTime.Nanoseconds(),
|
||||
DenyTTL: cacheTime.Nanoseconds(),
|
||||
RetryBackoff: 0,
|
||||
DefaultAllow: defaultAllow,
|
||||
}
|
||||
if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
// Create a new admission controller
|
||||
configFile, err := os.Open(pc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read test config: %v", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
wh, err := NewImagePolicyWebhook(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wh, err
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
clientCert, clientKey, clientCA []byte
|
||||
serverCert, serverKey, serverCA []byte
|
||||
wantAllowed, wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "TLS setup between client and server",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth",
|
||||
clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth, client provides it",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Client does not trust server",
|
||||
clientCert: clientCert, clientKey: clientKey,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not trust client",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
// Plugin does not support insecure configurations.
|
||||
test: "Server is using insecure connection",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, -1, false)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
pod := goodPod(strconv.Itoa(rand.Intn(1000)))
|
||||
attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
// Allow all and see if we get an error.
|
||||
service.Allow()
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission")
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit with AllowAll policy: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Deny()
|
||||
if err := wh.Validate(attr); err == nil {
|
||||
t.Errorf("%s: incorrectly admitted with DenyAll policy", tt.test)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type webhookCacheTestCase struct {
|
||||
statusCode int
|
||||
expectedErr bool
|
||||
expectedAuthorized bool
|
||||
expectedCached bool
|
||||
}
|
||||
|
||||
func testWebhookCacheCases(t *testing.T, serv *mockService, wh *Plugin, attr admission.Attributes, tests []webhookCacheTestCase) {
|
||||
for _, test := range tests {
|
||||
serv.statusCode = test.statusCode
|
||||
err := wh.Validate(attr)
|
||||
authorized := err == nil
|
||||
|
||||
if test.expectedErr && err == nil {
|
||||
t.Errorf("Expected error")
|
||||
} else if !test.expectedErr && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.expectedAuthorized && !authorized {
|
||||
if test.expectedCached {
|
||||
t.Errorf("Webhook should have successful response cached, but authorizer reported unauthorized.")
|
||||
} else {
|
||||
t.Errorf("Webhook returned HTTP %d, but authorizer reported unauthorized.", test.statusCode)
|
||||
}
|
||||
} else if !test.expectedAuthorized && authorized {
|
||||
t.Errorf("Webhook returned HTTP %d, but authorizer reported success.", test.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebhookCache verifies that error responses from the server are not
|
||||
// cached, but successful responses are.
|
||||
func TestWebhookCache(t *testing.T) {
|
||||
serv := new(mockService)
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Create an admission controller that caches successful responses.
|
||||
wh, err := newImagePolicyWebhook(s.URL, clientCert, clientKey, caCert, 200, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []webhookCacheTestCase{
|
||||
{statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 404, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 403, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 401, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
|
||||
{statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(goodPod("test"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
serv.allow = true
|
||||
|
||||
testWebhookCacheCases(t, serv, wh, attr, tests)
|
||||
|
||||
// For a different request, webhook should be called again.
|
||||
tests = []webhookCacheTestCase{
|
||||
{statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
|
||||
{statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
|
||||
}
|
||||
attr = admission.NewAttributesRecord(goodPod("test2"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
testWebhookCacheCases(t, serv, wh, attr, tests)
|
||||
}
|
||||
|
||||
func TestContainerCombinations(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
pod *api.Pod
|
||||
wantAllowed, wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "Single container allowed",
|
||||
pod: goodPod("good"),
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Single container denied",
|
||||
pod: goodPod("bad"),
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "One good container, one bad",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Multiple good containers",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "Multiple bad containers",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Good container, bad init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Bad container, good init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Good container, good init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission: %s", tt.test)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission: %s", tt.test)
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAllow(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
pod *api.Pod
|
||||
wantAllowed, wantErr, defaultAllow bool
|
||||
}{
|
||||
{
|
||||
test: "DefaultAllow = true, backend unreachable, bad image",
|
||||
pod: goodPod("bad"),
|
||||
defaultAllow: true,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = true, backend unreachable, good image",
|
||||
pod: goodPod("good"),
|
||||
defaultAllow: true,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = false, backend unreachable, good image",
|
||||
pod: goodPod("good"),
|
||||
defaultAllow: false,
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = false, backend unreachable, bad image",
|
||||
pod: goodPod("bad"),
|
||||
defaultAllow: false,
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 500
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, tt.defaultAllow)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission")
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// A service that can record annotations sent to it
|
||||
type annotationService struct {
|
||||
annotations map[string]string
|
||||
}
|
||||
|
||||
func (a *annotationService) Review(r *v1alpha1.ImageReview) {
|
||||
a.annotations = make(map[string]string)
|
||||
for k, v := range r.Spec.Annotations {
|
||||
a.annotations[k] = v
|
||||
}
|
||||
r.Status.Allowed = true
|
||||
}
|
||||
func (a *annotationService) HTTPStatusCode() int { return 200 }
|
||||
func (a *annotationService) Annotations() map[string]string { return a.annotations }
|
||||
|
||||
func TestAnnotationFiltering(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
annotations map[string]string
|
||||
outAnnotations map[string]string
|
||||
}{
|
||||
{
|
||||
test: "all annotations filtered out",
|
||||
annotations: map[string]string{
|
||||
"test": "test",
|
||||
"another": "annotation",
|
||||
"": "",
|
||||
},
|
||||
outAnnotations: map[string]string{},
|
||||
},
|
||||
{
|
||||
test: "image-policy annotations allowed",
|
||||
annotations: map[string]string{
|
||||
"my.image-policy.k8s.io/test": "test",
|
||||
"other.image-policy.k8s.io/test2": "annotation",
|
||||
"test": "test",
|
||||
"another": "another",
|
||||
"": "",
|
||||
},
|
||||
outAnnotations: map[string]string{
|
||||
"my.image-policy.k8s.io/test": "test",
|
||||
"other.image-policy.k8s.io/test2": "annotation",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(annotationService)
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, true)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
pod := goodPod("test")
|
||||
pod.Annotations = tt.annotations
|
||||
|
||||
attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.outAnnotations, service.Annotations()) {
|
||||
t.Errorf("expected annotations sent to webhook: %v to match expected: %v", service.Annotations(), tt.outAnnotations)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func goodPod(containerID string) *api.Pod {
|
||||
return &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: containerID,
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was generated using openssl by the gencerts.sh script
|
||||
// and holds raw certificates for the imagepolicy webhook tests.
|
||||
|
||||
package imagepolicy
|
||||
|
||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAoKjaP9PtRAGRNCx8z+0LTGt2eEduqElcPrm8EvlBwn3dnLFo
|
||||
55x+Tejb6ysQsyy1BKI0dRdX4tNSAgFFFaIVcsOo9kGtPq7QsSd4VWViNE3L5zJA
|
||||
+0X2ztHBkPlQXwDrtArsNKxwcpyHP9sXE05BN36XBjAz2XkusTkFrdJ/PzjZhlb4
|
||||
9i9gTZ0bJbexQ1+dfZX2WpY70JypYnKrbV1dLj5ORb65SC8IWZcG/ouqLWAN+lT+
|
||||
eug8P6PjoOQWs3qsl0bSAtAdiYcwXKtPiBEWPJe24ACywyE+8jVzmIJqAm0U1V8k
|
||||
GTHzjmSRwzgX/VN5JMri/nxNIW5UsbhHzYHfjQIDAQABAoIBAQCIeAWz1Bwl+ULT
|
||||
U7rNkChZyKrAbsUDdBVEPtcQMuR2Bh5Z/KUEoHz1RwiP0WwFFsPI5NO0ZpjD1wdB
|
||||
Jrz9LEoVyzfZvl4f8bTZ1pIzz8PEdBTxFVH3Xy3P7oMC15Q6rviIXgLYl2WJJYcJ
|
||||
adxHDOD+96vnmMhiQbq01aAKT9TA6PvXXDusfadMQ+il+mEbeZz4aNYBk9u+34Co
|
||||
aQTNwlLft5anW2820IMJdJR/bFjyX71cPID1rIjw4VOQZExIpIEnuHPiulyE4EvJ
|
||||
hvvVKAm0dRjHg39cz0eAQ6PntX3DUvjNfcLLrj7sQxLco1cnAKZxhpZ8ajtvynr5
|
||||
pF2d5xYBAoGBAM8y/e5+raHTLHEKZUc0vekUey3fc4aRqptyAKTS0ZvOYBXg4Vhl
|
||||
mOK7066IEqwF4UHGmQqW6D5HstqPGx0uN0d9IyImUqDp0JotdFSZMEMQkYLyFD+r
|
||||
J7O2nOO6E4SOxXO9/q9iSB+G/qgl6LS3O9+58uHTYEbUommiDZ6a18qBAoGBAMZ/
|
||||
xSGMa3b6vrU3rUTEh+xBh6YRVNYAxWwpGg2sO0k2brT3SxSMCrx1wvNGY+k7XNx0
|
||||
JJfZQDC/wlR0rcVTnPCi/cE9FTUlh23xXCPRlxwc4vLly+7yU95LhAO+N9XAwsrs
|
||||
OIi4lR57jxoLNO2ofoAVMvllkE5Eo5W6lOPR2xcNAoGAV1Tv0OFV//pJJhAypfOm
|
||||
BCLc1HX1dIfbOA+yE8bEEH7I4w/ZC3AvI4n1a//wls8Xpai2gs8ebnm7+gENdZww
|
||||
MpKdB1zNwQMsKH/2I146CFpoap/sRvW2EzpqIFYiueGPefxf575uFdPJbEgmMF13
|
||||
ABKZO/PjBZfEKO/j+7DaOYECgYBYX+Zqa1QlIrnpgKJZ7Y3+d6ZnH2w/4xQCdcIt
|
||||
uDKlA+ECHN+GhFr7UQq8uOgenNlZJTRtjsHvclCYvWHoarOCx25mrEVW5iCHqF+3
|
||||
asb2Mz4vmnPTLHx+iex6piPBvRJ8ufLpnBR3/9bUZ4znCo9XgxiwxLEcx551OR60
|
||||
12fNuQKBgC1fkqgtDDxQzrabSmmiqXthcPXxFdsYqnSNlFgba0uaAp9LREztSrX8
|
||||
QhwSoSwHVmjBvR6SybLYdsZ9Efj/w7XBejOOcS44MOoHYYFdsP7W47Ao5QFqvDoI
|
||||
oqyQ1R73cF9WX6obRQwH4P3DvcsBebOjvjMX9mljKtpJMc9KqrGc
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJAJlL10mfdZraMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgqNo/0+1EAZE0LHzP7QtM
|
||||
a3Z4R26oSVw+ubwS+UHCfd2csWjnnH5N6NvrKxCzLLUEojR1F1fi01ICAUUVohVy
|
||||
w6j2Qa0+rtCxJ3hVZWI0TcvnMkD7RfbO0cGQ+VBfAOu0Cuw0rHBynIc/2xcTTkE3
|
||||
fpcGMDPZeS6xOQWt0n8/ONmGVvj2L2BNnRslt7FDX519lfZaljvQnKlicqttXV0u
|
||||
Pk5FvrlILwhZlwb+i6otYA36VP566Dw/o+Og5BazeqyXRtIC0B2JhzBcq0+IERY8
|
||||
l7bgALLDIT7yNXOYgmoCbRTVXyQZMfOOZJHDOBf9U3kkyuL+fE0hblSxuEfNgd+N
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBSx2m5pJoFpdGDmOzSVl29jkheQFTAfBgNVHSME
|
||||
GDAWgBSx2m5pJoFpdGDmOzSVl29jkheQFTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBe6tZzmOQKt8fTsnDDKvEjSwK2Pb91R5tkwmIhdpTjmAgC+Zkk
|
||||
kSihR9sZIxdRC4wlbuorRl8BjhX5I8Kr3FWdDhOrIhicp7CIrxPiFh6+ZLSOj3o9
|
||||
pQ6SriIopjXCHvl5XjzKxLg/uQpzui/YUtfqffCRB4EccOsjlyUanK5rjMLBMLCn
|
||||
2LadiRB2Q/cC9fYigczETACDjq5vzp6I9eqwpCTmv/+4bFncW+VBD4touaJc8FKf
|
||||
ljW5xekKRh4uzP85X7rEgrFen/my5Fs/cylkFvYIiZwgn6NLgW3BNi+m31XIfU0S
|
||||
xIbgh4UH0dwc6Zk8WUwFud4GXj6OyGneMGKB
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAnKpC89q4/H+Xg91xI+GLhkrpJrO4n3nw0+/EQUoF9qwLtEDk
|
||||
mJp6ymUulwJgfvJwHOsUYqQB6jMKfXyqeSR24ssjjF9LTKhaQMZOGcW5Mshi04Ie
|
||||
USX93wDZwbWwqihVSqWaMpmf3JByeldnXNtc29Ik6NwqZcNWW5kEsSszheLhOU4i
|
||||
ZcRUlovwMYhHX37vQCQ1aygMaIMgBOb/vogSNxumqPKS4WdWsjss6LmEPnm350e+
|
||||
+9cb6RAfrDlOaj32VbLEp500SfBpZeCuyc5v81X12HT4V0qmsqIZ79tIgrqAaPxE
|
||||
D/HJXPpH64EAr23bR9dMPLXTh6w2yYWu+NGYywIDAQABAoIBAQCE/CZPN1gVxf0I
|
||||
i12x9o/oVAhruN08Sld6oCm4viwnws1AmmExhNg8m/0bZIIi4Ir4kThBrzSM5/y8
|
||||
nqlaofBk/cjULEQP80yBdZPwXp2hlOYG4on3mkdRGDjALQmktw4HimFFGJDRuq/i
|
||||
V/U+plrBojWAkPtQXKsen9qSxbg7qhI6KZyUQKExIHhsCfmE1ZzGx+/bgLVJEagi
|
||||
7zzZdAj2BzdoCk8yySAAsZG+pNSnd8gs5EzzRJ1RXanwxPSeEG/guX9YhLgLhhFu
|
||||
XzXngJDKVVhz4F2TfxtqIvZYvTMNh0R1OE0OUO2P88M837KKk5BHvW9oqYKZTUFV
|
||||
MC9k5No5AoGBAMtUBp8UcYZy+yetOAK2iGaEYwuWx8vwjY0c1POWun2Hny0nYxTQ
|
||||
WxXXqKaJydxZ+DlD3XuRKmMlKZQsp+bzuL5ukWN/ipO5tgQQfuKOZqVwvL19GkFi
|
||||
+Qr70G/TvYT/rv6A4s6XqbG4xt+7c2gf/XSghyoIyq1uwOcNNtrMdM/tAoGBAMU/
|
||||
tYc4d+vAl7hd8TwhFiZiC3N84C1HwsPVj38uqQI/j8boB21Bhpw6HHzq+VdVPfvp
|
||||
zk5e8AiQdSpitM7pBVmLpoRdTQjdlUDFRUi4TdJwfp5P7dXM8D6swNQ9f9w180na
|
||||
5ewu16PSC+sh19wAl04KwOmiDqZujJrBgWnFcESXAoGBALGofoybAUK3zqlxWcJN
|
||||
GUtyG1Sx72tLiXMmIQ+hwNsUGEoM4y75isy//ZVeSammVxQ6Lxjb00yD2RumFSLg
|
||||
C6kg1Ro6A6xmFRriCuwL/rZJljB/UeSWBQLK2eoL+clu2sl3djWLIPOvft1YXVM6
|
||||
uGwiI1fgDK+TWSvJSQfOo7ZVAoGBAK+A6DvQeqNBUb2xmJsvtU2hnx661Zx0ZU9q
|
||||
DavUEHz3oS4R9cm4q9UFv6NGT2Tta6FhfzcsMdbs8dMs0EPqAeCS6S6M9aYVwl9H
|
||||
J0Z09olvnrmt1KiPGJQrkcdGkSWWu0nTgxCK/UO9+OzVyALwY7AE0XEPyIk9g82O
|
||||
r181VZcxAoGANY2QGYrNtfa++o2B0O4qskKxhYEeCnZPptmjVO0oHOx2YSDQXK3K
|
||||
B0evCQ7ylvMnobNLjp9bqD14a0M86QjRlpSg1vHUhBsETZICc+E0UgV28CdWgYtt
|
||||
urARDE9ZpLVSRfPVAitC1I76pZwevsbQ9TeS2p0cWQpYYKmBtGpkdug=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJANQEJyMW4HFZMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcqkLz2rj8f5eD3XEj4YuG
|
||||
Sukms7ifefDT78RBSgX2rAu0QOSYmnrKZS6XAmB+8nAc6xRipAHqMwp9fKp5JHbi
|
||||
yyOMX0tMqFpAxk4ZxbkyyGLTgh5RJf3fANnBtbCqKFVKpZoymZ/ckHJ6V2dc21zb
|
||||
0iTo3Cplw1ZbmQSxKzOF4uE5TiJlxFSWi/AxiEdffu9AJDVrKAxogyAE5v++iBI3
|
||||
G6ao8pLhZ1ayOyzouYQ+ebfnR7771xvpEB+sOU5qPfZVssSnnTRJ8Gll4K7Jzm/z
|
||||
VfXYdPhXSqayohnv20iCuoBo/EQP8clc+kfrgQCvbdtH10w8tdOHrDbJha740ZjL
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBRjFVG818hHK+HSEhdz+gPwSKa4kzAfBgNVHSME
|
||||
GDAWgBRjFVG818hHK+HSEhdz+gPwSKa4kzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBBCl0UJq0iLy/dvym79mnoPZ1KPhS2WnQB5ZLzJwL26ePkr8j8
|
||||
G/1AOVPu73hovJx51b+T7ZhTgtmAEwqpRHBxRQ0+Yf973YOVJYp4QFGWDnueurzv
|
||||
bCsnZEPkQtccHzZxT3fUsM6Ejy99j0WBNmvfAj1X7yNaN5EZw6kvuaDDda3I7WNM
|
||||
0eGy8aoAcPJZkYfZb39VDq/qJn+bVsAJdUaXt/FkDZBJl6XzoGjC/webjRJOpkgN
|
||||
vgjJDhhQ8LlHFiq+lXIiK4Y55RBWG3iXGTM8W3fjZYTNvH7FlGyuRD4Y4hyaYXTP
|
||||
+PoFWuDZM89EAyICr0yyTc8mkdrAEM/Lj9GO
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAyhmjG7BJCGwuf1FyHJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTH
|
||||
riPOe9d1ahH7bvsZycnzh7/pABdTDUdStiR8/1KUYt8PjjosrmmYyupqNPq+wkBD
|
||||
EmKa+4voR2EBgXbIGghx8e++KmmNnSCNk6B8m2EJR0fn9zPnoY3uHNogKjCICt19
|
||||
g+uipuwZco7yTu3e40LwpIVmA8SsrM0S/CaZqSmtIClSwv7YDvreUd6FuI/GT0cj
|
||||
NMPRuSdfohBxGz6R7Cml7qP4AYKajjl+08mRYv3o+hVclXUltcRmTnJanYGmGS3k
|
||||
C7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS0PhjmQIDAQABAoIBAFOicmZ1+HM82a0k
|
||||
llWSV5xPUzUmU6TT4bJlZnzJd0R7i+6H8250MPH9AwEHOgb+cPiZ02cdGx5HiL4Z
|
||||
AviPdw7uLKwR5U0VdAIlfu6SPat5DNI0Z81G8x4gEtrfIRFjh4GGdykI7qh8j/cz
|
||||
ToOGSaq/aGiQMEWTvEqWArD7742lVHE4/1bM3GuKV8shy31zfw0d9RCCy1GdBR75
|
||||
zZ1w4zKL55DM3PC73Ndy2IcrViVXVAgfqD0xxKwQW1qoENgThueALj3PkU1XaKxI
|
||||
nOdztt1fBFpcSHyFBkJ1sexumnssMRXSVcJ/0D5F2T4QPUnWBM0oSzoyioAab4RP
|
||||
8XrZwAECgYEA/eFjNgCeHztXgS3YRC/RddLOtobrerYKN7vA64ou5VUCqEQ9rfQE
|
||||
MbmKdZdiFVNJI0JrPq8Gx39ME9g2OLTVVqdtlm6JYjy5CHdUXHIHObo9oz7Uueos
|
||||
TdeCf0LFvEUNXvbGIP5KqcdVi+wekauHMqXGQYTNa6bar/FE99MdyAECgYEAy8mU
|
||||
tCjm4QsuKsdku5bDHGv56ZN9DkWd7Lcjie5otElwH9bKfIQ2lUYyoUAIa0rEJ9Ya
|
||||
7vuAZ2bX7od9s8Jkci91ONDWxdy361SRZcbpuqgQKKVRuzGlfamufyW4sStbXY1k
|
||||
+zeQxyWGJHhhLWpapzca89RELGZSkbIMVVIT25kCgYEA7EUYboZuoYQ5cGf476RM
|
||||
28kfRXEUrvPBWJLr/IhyEk1mFrDDciM40AnrWHpU9qG23BCQ/BopRforFADQnT91
|
||||
l5pje29NfdYjIUTkhtA79zZi7IyprofHSX453TOIECl3QxyH0Oa3F4ACFiDdZhXq
|
||||
0XDDq+/quLfkp37y/2xDOAECgYEAmi55g5UumTWMSHFzlToLhIVtH3unMhUZ1u74
|
||||
xHLMZRrq6ivoJy0g3u+tfrKjrAl1P26OEiHWlGULGj0Ireh1dq7RUZsv46OKw1HI
|
||||
b+h/Den5z8bEf4ygWOL4UtqHUgQrrCw+KpNvxjxtsUoiu+mrjLf0fGYs7iq8bd73
|
||||
1dWzkIECgYEAi6P/LzMC6orbyONmwlscqO1Ili8ZBkUjJ/wThkiNMMA3pyKmb68W
|
||||
yt56Yh0rs+WnuVUN90cG87k+CY35dQ7FAOVUJi9LWGA3Oq9fGkoOB7f4dzaUu/rB
|
||||
dtit2KPCxiKpZsxqSf4+S8AXYF48abNPLYK3DCCSqAah09gYOrqYlW4=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAMvo2rkGpEUQMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfc2Vy
|
||||
dmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyhmjG7BJCGwuf1Fy
|
||||
HJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTHriPOe9d1ahH7bvsZycnzh7/pABdTDUdS
|
||||
tiR8/1KUYt8PjjosrmmYyupqNPq+wkBDEmKa+4voR2EBgXbIGghx8e++KmmNnSCN
|
||||
k6B8m2EJR0fn9zPnoY3uHNogKjCICt19g+uipuwZco7yTu3e40LwpIVmA8SsrM0S
|
||||
/CaZqSmtIClSwv7YDvreUd6FuI/GT0cjNMPRuSdfohBxGz6R7Cml7qP4AYKajjl+
|
||||
08mRYv3o+hVclXUltcRmTnJanYGmGS3kC7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS
|
||||
0PhjmQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCF
|
||||
xaS/KIijKDLbaL/P7AxhnAta8jYSEzL66WTaYV4GeRhLtX/vPUV9gzPWnkNr0TBM
|
||||
lS+Q0KDxh17rJ/MrWwrMSwsgKZahTR+7mSHiXrIlHcnHXXSvhnoXu8VDu8goqOEI
|
||||
5yRHt6plzmFZEwVi/hSmIAuQjmyjOk2dc/ZKI0fMExKhnVms8AoztjAMbt3TFMTK
|
||||
Kk7bVGPblFsXiVPhRlzbLbh5i/PvHHf+12ACrVxoxOOQUmuXy1DPxmkk7jP3FIsE
|
||||
+rnyWnfmGS5sW8oMkj2nFYIh3LehADsMS9s7JVlJk/loNJDA9Yn2fev/vRKck8RZ
|
||||
siw54G4e+6nKpY5BAY1M
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3IOqCz88jTQpsGIBFTdjbqBg+0NFeym3OEl8zLfzkLQuZieO
|
||||
3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWIY8KU7c2SPfErlhP86VFoD0RKHJxwRVh0
|
||||
y70WyK8+CzzwrrPpWydgtAwbm9F+0v/zdcCL0TEL2/MYgCc97mSGwtTRaW4bqq6V
|
||||
MWMHBcOu44dHq8+CF8ixxk0WSBl2oocXnF7QdEA15iuOM5hacLB0fyH4T3NM54lO
|
||||
rOSXUMUuysougSrMcCPv3esFlv4TVUkldwu73jWx+Wja0gNXlnmgU2lqFdM+PsVT
|
||||
DPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkNcx9QDQIDAQABAoIBADblRCC2pmFUmghB
|
||||
7ZkVh9hTbrE+Zv6pPOZzTPE93hGo+WAO+v6GNBLuIEte87DhF2QTmovp4VfsFeXK
|
||||
oECNgTvOEFkBP+OFqFGBJZGfY3/J5h0tTy4lLZXaImzzx8sGGNLLc8R+uyTIO3VV
|
||||
qIso2uXB+vzPgMrueflt5yp7hoJjI0c+qEktUg5n+WJFAFteI9LCngN+xwRWVEgp
|
||||
rjKVPcT9zio8tLJOhcSPA7q6lORUkwbPWHyNDpamvldnqjhgp5Ceq5f/qfoWPzvM
|
||||
H5o72Ax2WduxST+P+hCOqZReUmTaGzAKb5rJwdEpmbnDZ3kSR08aT/40m/EG1SvQ
|
||||
pi0b3QECgYEA/mRGIjaYPQr+tw3Sz8g76t3PYfrglro60HdLBn2IUpj2sEpazNId
|
||||
2aPFPb58whL+VPmUfXbpPH+wW/+wWpRw4MraFkJanbOjDiEGXK5ZoUQIDZJWUSwf
|
||||
oCge5uacU69weC67UyPYmK1e+A/gaFw1Dz729jLxtB3rGWKxEGbWEc0CgYEA3eiP
|
||||
hv0GxbdEEbSfQoSPKbBHGI9spaqAIcqL+dSsx3m6Ckqx0El/xi9mQkITgqs2gyqI
|
||||
o2T/3yDli9oF4+3Plz0wrZ11auOWX+nhKfACtF679I1PL0UOavXF0FVgOfwOIqdG
|
||||
jp4QQV7USkbTP9ZOHo90Y8G4rmTEdMZ/VsH490ECgYEA8u/bsiyk8haf7Tx8SAWW
|
||||
gtLUi2NEO20ZYZ+qvEYBe6+sVeqMD/HQo9ksMazKA6ST0Z6O2cpHLolaaGEjjz0X
|
||||
FvVhk8RGOTglzQZoxvWRjtojPqKzX81dXlsyN5ufSqPOKlemeN1QqW1XtlmjGsaD
|
||||
vU2KFs/L1xCDRbjkEx/B6zkCgYBmqeE9InKvpknnpxjHPWy+bL93rWMmgesltv9r
|
||||
ZelJoBdiC4yYQGjM18EHhmpgWbWumU79yQxXvnB0czmmaa9Q2Q5cRCy+duxrE1kI
|
||||
ffHCYNG0ImwwAlLZSTtrVxRdvy8K+Ti7YoVCuQyeEIZLUmpx2QyP2mAGzrfVDsB6
|
||||
8uKsAQKBgQDO+PmADra91NKJP1iVuvOK8iEy/Z14L03uKtF3X9u8vLdzQZa/Q/P9
|
||||
hXOX9ovFwSBQOOfgb+/+QRuPL4xxi1J8CFwrSWCEeFgrDijl9DS6aNY6BWHDA8p6
|
||||
8V7Adb04cnenj8QjYYN8/mqsQlHSoAIxeAlUoJpq+pk7O8PAfbjgMw==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC+jCCAeKgAwIBAgIJAMvo2rkGpEURMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2xp
|
||||
ZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3IOqCz88jTQpsGIB
|
||||
FTdjbqBg+0NFeym3OEl8zLfzkLQuZieO3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWI
|
||||
Y8KU7c2SPfErlhP86VFoD0RKHJxwRVh0y70WyK8+CzzwrrPpWydgtAwbm9F+0v/z
|
||||
dcCL0TEL2/MYgCc97mSGwtTRaW4bqq6VMWMHBcOu44dHq8+CF8ixxk0WSBl2oocX
|
||||
nF7QdEA15iuOM5hacLB0fyH4T3NM54lOrOSXUMUuysougSrMcCPv3esFlv4TVUkl
|
||||
dwu73jWx+Wja0gNXlnmgU2lqFdM+PsVTDPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkN
|
||||
cx9QDQIDAQABoy8wLTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAkHIhrPfRROhzLg2hRZz5/7Kw
|
||||
3V0/Y0XS91YU3rew+c2k++bLp1INzpWxfB6gbSC6bTOgn/seIDvxwJ2g5DRdOxU/
|
||||
Elcpqg1hTCVfpmra9PCniMzZuP7lsz8sJKj6FgE6ElJ1S74FW/CYz/jA+76LLot4
|
||||
JwGkCJHzyLgFPBEOjJ/mLYSM/SDzHU5E+NHXVaKz4MjM3JwycN/juqi4ikAcZEBW
|
||||
1HmpcHKBedAwlCM90zlvG2SL4sFRp/clMbntRdmh5L+/1F6aP82PO3iuvXtXP48d
|
||||
NtjboxP3IV2eY5iUle8BOQ9CnFQs4wsF1LxTMNACypQyFinMsHrCpwrB3i4VvA==
|
||||
-----END CERTIFICATE-----`)
|
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRetryBackoff = time.Duration(500) * time.Millisecond
|
||||
minRetryBackoff = time.Duration(1)
|
||||
maxRetryBackoff = time.Duration(5) * time.Minute
|
||||
defaultAllowTTL = time.Duration(5) * time.Minute
|
||||
defaultDenyTTL = time.Duration(30) * time.Second
|
||||
minAllowTTL = time.Duration(1) * time.Second
|
||||
maxAllowTTL = time.Duration(30) * time.Minute
|
||||
minDenyTTL = time.Duration(1) * time.Second
|
||||
maxDenyTTL = time.Duration(30) * time.Minute
|
||||
useDefault = time.Duration(0) //sentinel for using default TTL
|
||||
disableTTL = time.Duration(-1) //sentinel for disabling a TTL
|
||||
)
|
||||
|
||||
// imagePolicyWebhookConfig holds config data for imagePolicyWebhook
|
||||
type imagePolicyWebhookConfig struct {
|
||||
KubeConfigFile string `json:"kubeConfigFile"`
|
||||
AllowTTL time.Duration `json:"allowTTL"`
|
||||
DenyTTL time.Duration `json:"denyTTL"`
|
||||
RetryBackoff time.Duration `json:"retryBackoff"`
|
||||
DefaultAllow bool `json:"defaultAllow"`
|
||||
}
|
||||
|
||||
// AdmissionConfig holds config data for admission controllers
|
||||
type AdmissionConfig struct {
|
||||
ImagePolicyWebhook imagePolicyWebhookConfig `json:"imagePolicy"`
|
||||
}
|
||||
|
||||
func normalizeWebhookConfig(config *imagePolicyWebhookConfig) (err error) {
|
||||
config.RetryBackoff, err = normalizeConfigDuration("backoff", time.Millisecond, config.RetryBackoff, minRetryBackoff, maxRetryBackoff, defaultRetryBackoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.AllowTTL, err = normalizeConfigDuration("allow cache", time.Second, config.AllowTTL, minAllowTTL, maxAllowTTL, defaultAllowTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.DenyTTL, err = normalizeConfigDuration("deny cache", time.Second, config.DenyTTL, minDenyTTL, maxDenyTTL, defaultDenyTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeConfigDuration(name string, scale, value, min, max, defaultValue time.Duration) (time.Duration, error) {
|
||||
// disable with -1 sentinel
|
||||
if value == disableTTL {
|
||||
glog.V(2).Infof("image policy webhook %s disabled", name)
|
||||
return time.Duration(0), nil
|
||||
}
|
||||
|
||||
// use default with 0 sentinel
|
||||
if value == useDefault {
|
||||
glog.V(2).Infof("image policy webhook %s using default value", name)
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
// convert to s; unmarshalling gives ns
|
||||
value *= scale
|
||||
|
||||
// check value is within range
|
||||
if value < min || value > max {
|
||||
return value, fmt.Errorf("valid value is between %v and %v, got %v", min, max, value)
|
||||
}
|
||||
return value, nil
|
||||
}
|
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConfigNormalization(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
config imagePolicyWebhookConfig
|
||||
normalizedConfig imagePolicyWebhookConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "config within normal ranges",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second,
|
||||
RetryBackoff: ((minRetryBackoff + maxRetryBackoff) / 2) / time.Millisecond,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second * time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second * time.Second,
|
||||
RetryBackoff: (minRetryBackoff + maxRetryBackoff) / 2,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config below normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL - time.Duration(1),
|
||||
DenyTTL: minDenyTTL - time.Duration(1),
|
||||
RetryBackoff: minRetryBackoff - time.Duration(1),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config above normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(1) + maxAllowTTL,
|
||||
DenyTTL: time.Duration(1) + maxDenyTTL,
|
||||
RetryBackoff: time.Duration(1) + maxRetryBackoff,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config wants default values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: useDefault,
|
||||
DenyTTL: useDefault,
|
||||
RetryBackoff: useDefault,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: defaultAllowTTL,
|
||||
DenyTTL: defaultDenyTTL,
|
||||
RetryBackoff: defaultRetryBackoff,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config wants disabled values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: disableTTL,
|
||||
DenyTTL: disableTTL,
|
||||
RetryBackoff: disableTTL,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(0),
|
||||
DenyTTL: time.Duration(0),
|
||||
RetryBackoff: time.Duration(0),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config within normal ranges for min values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL / time.Second,
|
||||
DenyTTL: minDenyTTL / time.Second,
|
||||
RetryBackoff: minRetryBackoff,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL,
|
||||
DenyTTL: minDenyTTL,
|
||||
RetryBackoff: minRetryBackoff * time.Millisecond,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config within normal ranges for max values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: maxAllowTTL / time.Second,
|
||||
DenyTTL: maxDenyTTL / time.Second,
|
||||
RetryBackoff: maxRetryBackoff / time.Millisecond,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: maxAllowTTL,
|
||||
DenyTTL: maxDenyTTL,
|
||||
RetryBackoff: maxRetryBackoff,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
err := normalizeWebhookConfig(&tt.config)
|
||||
if err == nil && tt.wantErr == true {
|
||||
t.Errorf("%s: expected error from normalization and didn't have one", tt.test)
|
||||
}
|
||||
if err != nil && tt.wantErr == false {
|
||||
t.Errorf("%s: unexpected error from normalization: %v", tt.test, err)
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(tt.config, tt.normalizedConfig) {
|
||||
t.Errorf("%s: expected config to be normalized. got: %v expected: %v", tt.test, tt.config, tt.normalizedConfig)
|
||||
}
|
||||
}
|
||||
}
|
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package imagepolicy checks a webhook for image admission
|
||||
package imagepolicy // import "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2016 The Kubernetes Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -e
|
||||
|
||||
# gencerts.sh generates the certificates for the webhook authz plugin tests.
|
||||
#
|
||||
# It is not expected to be run often (there is no go generate rule), and mainly
|
||||
# exists for documentation purposes.
|
||||
|
||||
cat > server.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
EOF
|
||||
|
||||
cat > client.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
EOF
|
||||
|
||||
# Create a certificate authority
|
||||
openssl genrsa -out caKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=webhook_imagepolicy_ca"
|
||||
|
||||
# Create a second certificate authority
|
||||
openssl genrsa -out badCAKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=webhook_imagepolicy_ca"
|
||||
|
||||
# Create a server certiticate
|
||||
openssl genrsa -out serverKey.pem 2048
|
||||
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook_imagepolicy_server" -config server.conf
|
||||
openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf
|
||||
|
||||
# Create a client certiticate
|
||||
openssl genrsa -out clientKey.pem 2048
|
||||
openssl req -new -key clientKey.pem -out client.csr -subj "/CN=webhook_imagepolicy_client" -config client.conf
|
||||
openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf
|
||||
|
||||
outfile=certs_test.go
|
||||
|
||||
cat > $outfile << EOF
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
EOF
|
||||
|
||||
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
||||
echo "// and holds raw certificates for the imagepolicy webhook tests." >> $outfile
|
||||
echo "" >> $outfile
|
||||
echo "package imagepolicy" >> $outfile
|
||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
||||
data=$(cat ${file}.pem)
|
||||
echo "" >> $outfile
|
||||
echo "var $file = []byte(\`$data\`)" >> $outfile
|
||||
done
|
||||
|
||||
# Clean up after we're done.
|
||||
rm *.pem
|
||||
rm *.csr
|
||||
rm *.srl
|
||||
rm *.conf
|
Reference in New Issue
Block a user