Proxy init and sidecar containers auto-injection (#1714)

* Support auto sidecar-injection

1. Add proxy-injector deployment spec to cli/install/template.go
2. Inject the Linkerd CA bundle into the MutatingWebhookConfiguration
during the webhook's start-up process.
3. Add a new handler to the CA controller to create a new secret for the
webhook when a new MutatingWebhookConfiguration is created.
4. Declare a config map to store the proxy and proxy-init container
specs used during the auto-inject process.
5. Ignore namespace and pods that are labeled with
linkerd.io/auto-inject: disabled or linkerd.io/auto-inject: completed
6. Add new flag to `linkerd install` to enable/disable proxy
auto-injection

Proposed implementation for #561.

* Resolve missing packages errors
* Move the auto-inject label to the pod level
* PR review items
* Move proxy-injector to its own deployment
* Ignore pods that already have proxy injected

This ensures the webhook doesn't error out due to proxy that are injected using the  command

* PR review items on creating/updating the MWC on-start
* Replace API calls to ConfigMap with file reads
* Fixed post-rebase broken tests
* Don't mutate the auto-inject label

Since we started using healhcheck.HasExistingSidecars() to ensure pods with
existing proxies aren't mutated, we don't need to use the auto-inject label as
an indicator.

This resolves a bug which happens with the kubectl run command where the deployment
is also assigned the auto-inject label. The mutation causes the pod auto-inject
label to not match the deployment label, causing kubectl run to fail.

* Tidy up unit tests
* Include proxy resource requests in sidecar config map
* Fixes to broken YAML in CLI install config

The ignore inbound and outbound ports are changed to string type to
avoid broken YAML caused by the string conversion in the uint slice.

Also, parameterized the proxy bind timeout option in template.go.

Renamed the sidecar config map to
'linkerd-proxy-injector-webhook-config'.

Signed-off-by: ihcsim <ihcsim@gmail.com>
This commit is contained in:
Ivan Sim 2018-10-10 12:09:22 -07:00 committed by Kevin Lingerfelt
parent db37c5a007
commit 4fba6aca0a
55 changed files with 3310 additions and 110 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ web/app/yarn-error.log
.protoc
.gorun
.dep*
**/*.swp

View File

@ -533,9 +533,10 @@
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
[[projects]]
digest = "1:34ffbf9ed5e63a11e4e0aaab597dc36c552da8b5b6bd49d8f73dadd4afd7e677"
digest = "1:728c0c37966b7a6c8980fab69a3d690cc0996e206804a55052318858d00f1e17"
name = "k8s.io/api"
packages = [
"admission/v1beta1",
"admissionregistration/v1alpha1",
"admissionregistration/v1beta1",
"apps/v1",
@ -848,6 +849,8 @@
"google.golang.org/grpc/codes",
"google.golang.org/grpc/metadata",
"google.golang.org/grpc/status",
"k8s.io/api/admission/v1beta1",
"k8s.io/api/admissionregistration/v1beta1",
"k8s.io/api/apps/v1",
"k8s.io/api/apps/v1beta2",
"k8s.io/api/authorization/v1beta1",
@ -861,12 +864,14 @@
"k8s.io/apimachinery/pkg/labels",
"k8s.io/apimachinery/pkg/runtime",
"k8s.io/apimachinery/pkg/runtime/schema",
"k8s.io/apimachinery/pkg/runtime/serializer",
"k8s.io/apimachinery/pkg/util/intstr",
"k8s.io/apimachinery/pkg/util/runtime",
"k8s.io/apimachinery/pkg/util/wait",
"k8s.io/apimachinery/pkg/util/yaml",
"k8s.io/apimachinery/pkg/version",
"k8s.io/client-go/informers",
"k8s.io/client-go/informers/admissionregistration/v1beta1",
"k8s.io/client-go/informers/apps/v1beta2",
"k8s.io/client-go/informers/core/v1",
"k8s.io/client-go/kubernetes",

View File

@ -1,5 +1,5 @@
## compile binaries
FROM gcr.io/linkerd-io/go-deps:64a32a2a as golang
FROM gcr.io/linkerd-io/go-deps:0c590d0e as golang
WORKDIR /go/src/github.com/linkerd/linkerd2
COPY cli cli
COPY controller/k8s controller/k8s

View File

@ -11,6 +11,7 @@ import (
"strings"
"github.com/ghodss/yaml"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/spf13/cobra"
appsV1 "k8s.io/api/apps/v1"
@ -42,10 +43,6 @@ const (
)
type injectOptions struct {
inboundPort uint
outboundPort uint
ignoreInboundPorts []uint
ignoreOutboundPorts []uint
*proxyConfigOptions
}
@ -64,11 +61,7 @@ type objMeta struct {
func newInjectOptions() *injectOptions {
return &injectOptions{
inboundPort: 4143,
outboundPort: 4140,
ignoreInboundPorts: nil,
ignoreOutboundPorts: nil,
proxyConfigOptions: newProxyConfigOptions(),
proxyConfigOptions: newProxyConfigOptions(),
}
}
@ -107,10 +100,6 @@ sub-folder. e.g. linkerd inject <folder> | kubectl apply -f -
}
addProxyConfigFlags(cmd, options.proxyConfigOptions)
cmd.PersistentFlags().UintVar(&options.inboundPort, "inbound-port", options.inboundPort, "Proxy port to use for inbound traffic")
cmd.PersistentFlags().UintVar(&options.outboundPort, "outbound-port", options.outboundPort, "Proxy port to use for outbound traffic")
cmd.PersistentFlags().UintSliceVar(&options.ignoreInboundPorts, "skip-inbound-ports", options.ignoreInboundPorts, "Ports that should skip the proxy and send directly to the application")
cmd.PersistentFlags().UintSliceVar(&options.ignoreOutboundPorts, "skip-outbound-ports", options.ignoreOutboundPorts, "Outbound ports that should skip the proxy")
return cmd
}
@ -182,7 +171,7 @@ func injectObjectMeta(t *metaV1.ObjectMeta, k8sLabels map[string]string, options
*/
func injectPodSpec(t *v1.PodSpec, identity k8s.TLSIdentity, controlPlaneDNSNameOverride string, options *injectOptions, report *injectReport) bool {
report.hostNetwork = t.HostNetwork
report.sidecar = checkSidecars(t)
report.sidecar = healthcheck.HasExistingSidecars(t)
report.udp = checkUDPPorts(t)
// Skip injection if:
@ -751,31 +740,3 @@ func checkUDPPorts(t *v1.PodSpec) bool {
}
return false
}
func checkSidecars(t *v1.PodSpec) bool {
// check for known proxies and initContainers
for _, container := range t.Containers {
if strings.HasPrefix(container.Image, "gcr.io/linkerd-io/proxy:") ||
strings.HasPrefix(container.Image, "gcr.io/istio-release/proxyv2:") ||
strings.HasPrefix(container.Image, "gcr.io/heptio-images/contour:") ||
strings.HasPrefix(container.Image, "docker.io/envoyproxy/envoy-alpine:") ||
container.Name == "linkerd-proxy" ||
container.Name == "istio-proxy" ||
container.Name == "contour" ||
container.Name == "envoy" {
return true
}
}
for _, ic := range t.InitContainers {
if strings.HasPrefix(ic.Image, "gcr.io/linkerd-io/proxy-init:") ||
strings.HasPrefix(ic.Image, "gcr.io/istio-release/proxy_init:") ||
strings.HasPrefix(ic.Image, "gcr.io/heptio-images/contour:") ||
ic.Name == "linkerd-init" ||
ic.Name == "istio-init" ||
ic.Name == "envoy-initconfig" {
return true
}
}
return false
}

View File

@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
"os"
"strings"
"text/template"
"github.com/linkerd/linkerd2/cli/install"
@ -16,24 +17,47 @@ import (
)
type installConfig struct {
Namespace string
ControllerImage string
WebImage string
PrometheusImage string
GrafanaImage string
ControllerReplicas uint
WebReplicas uint
PrometheusReplicas uint
ImagePullPolicy string
UUID string
CliVersion string
ControllerLogLevel string
ControllerComponentLabel string
CreatedByAnnotation string
ProxyAPIPort uint
EnableTLS bool
TLSTrustAnchorConfigMapName string
ProxyContainerName string
Namespace string
ControllerImage string
WebImage string
PrometheusImage string
GrafanaImage string
ControllerReplicas uint
WebReplicas uint
PrometheusReplicas uint
ImagePullPolicy string
UUID string
CliVersion string
ControllerLogLevel string
ControllerComponentLabel string
CreatedByAnnotation string
ProxyAPIPort uint
EnableTLS bool
TLSTrustAnchorConfigMapName string
ProxyContainerName string
TLSTrustAnchorFileName string
TLSCertFileName string
TLSPrivateKeyFileName string
TLSTrustAnchorVolumeSpecFileName string
TLSIdentityVolumeSpecFileName string
InboundPort uint
OutboundPort uint
IgnoreInboundPorts string
IgnoreOutboundPorts string
ProxyAutoInjectEnabled bool
ProxyAutoInjectLabel string
ProxyUID int64
ProxyMetricsPort uint
ProxyControlPort uint
ProxyInjectorTLSSecret string
ProxyInjectorSidecarConfig string
ProxySpecFileName string
ProxyInitSpecFileName string
ProxyInitImage string
ProxyImage string
ProxyResourceRequestCPU string
ProxyResourceRequestMemory string
ProxyBindTimeout string
}
type installOptions struct {
@ -41,6 +65,7 @@ type installOptions struct {
webReplicas uint
prometheusReplicas uint
controllerLogLevel string
proxyAutoInject bool
*proxyConfigOptions
}
@ -52,6 +77,7 @@ func newInstallOptions() *installOptions {
webReplicas: 1,
prometheusReplicas: 1,
controllerLogLevel: "info",
proxyAutoInject: false,
proxyConfigOptions: newProxyConfigOptions(),
}
}
@ -78,6 +104,7 @@ func newCmdInstall() *cobra.Command {
cmd.PersistentFlags().UintVar(&options.webReplicas, "web-replicas", options.webReplicas, "Replicas of the web server to deploy")
cmd.PersistentFlags().UintVar(&options.prometheusReplicas, "prometheus-replicas", options.prometheusReplicas, "Replicas of prometheus to deploy")
cmd.PersistentFlags().StringVar(&options.controllerLogLevel, "controller-log-level", options.controllerLogLevel, "Log level for the controller and web components")
cmd.PersistentFlags().BoolVar(&options.proxyAutoInject, "proxy-auto-inject", options.proxyAutoInject, "Enable proxy sidecar auto-injection webhook; default to false")
return cmd
}
@ -86,25 +113,61 @@ func validateAndBuildConfig(options *installOptions) (*installConfig, error) {
if err := validate(options); err != nil {
return nil, err
}
ignoreInboundPorts := []string{
fmt.Sprintf("%d", options.proxyControlPort),
fmt.Sprintf("%d", options.proxyMetricsPort),
}
for _, p := range options.ignoreInboundPorts {
ignoreInboundPorts = append(ignoreInboundPorts, fmt.Sprintf("%d", p))
}
ignoreOutboundPorts := []string{}
for _, p := range options.ignoreOutboundPorts {
ignoreOutboundPorts = append(ignoreOutboundPorts, fmt.Sprintf("%d", p))
}
return &installConfig{
Namespace: controlPlaneNamespace,
ControllerImage: fmt.Sprintf("%s/controller:%s", options.dockerRegistry, options.linkerdVersion),
WebImage: fmt.Sprintf("%s/web:%s", options.dockerRegistry, options.linkerdVersion),
PrometheusImage: "prom/prometheus:v2.4.0",
GrafanaImage: fmt.Sprintf("%s/grafana:%s", options.dockerRegistry, options.linkerdVersion),
ControllerReplicas: options.controllerReplicas,
WebReplicas: options.webReplicas,
PrometheusReplicas: options.prometheusReplicas,
ImagePullPolicy: options.imagePullPolicy,
UUID: uuid.NewV4().String(),
CliVersion: k8s.CreatedByAnnotationValue(),
ControllerLogLevel: options.controllerLogLevel,
ControllerComponentLabel: k8s.ControllerComponentLabel,
CreatedByAnnotation: k8s.CreatedByAnnotation,
ProxyAPIPort: options.proxyAPIPort,
EnableTLS: options.enableTLS(),
TLSTrustAnchorConfigMapName: k8s.TLSTrustAnchorConfigMapName,
ProxyContainerName: k8s.ProxyContainerName,
Namespace: controlPlaneNamespace,
ControllerImage: fmt.Sprintf("%s/controller:%s", options.dockerRegistry, options.linkerdVersion),
WebImage: fmt.Sprintf("%s/web:%s", options.dockerRegistry, options.linkerdVersion),
PrometheusImage: "prom/prometheus:v2.4.0",
GrafanaImage: fmt.Sprintf("%s/grafana:%s", options.dockerRegistry, options.linkerdVersion),
ControllerReplicas: options.controllerReplicas,
WebReplicas: options.webReplicas,
PrometheusReplicas: options.prometheusReplicas,
ImagePullPolicy: options.imagePullPolicy,
UUID: uuid.NewV4().String(),
CliVersion: k8s.CreatedByAnnotationValue(),
ControllerLogLevel: options.controllerLogLevel,
ControllerComponentLabel: k8s.ControllerComponentLabel,
CreatedByAnnotation: k8s.CreatedByAnnotation,
ProxyAPIPort: options.proxyAPIPort,
EnableTLS: options.enableTLS(),
TLSTrustAnchorConfigMapName: k8s.TLSTrustAnchorConfigMapName,
ProxyContainerName: k8s.ProxyContainerName,
TLSTrustAnchorFileName: k8s.TLSTrustAnchorFileName,
TLSCertFileName: k8s.TLSCertFileName,
TLSPrivateKeyFileName: k8s.TLSPrivateKeyFileName,
TLSTrustAnchorVolumeSpecFileName: k8s.TLSTrustAnchorVolumeSpecFileName,
TLSIdentityVolumeSpecFileName: k8s.TLSIdentityVolumeSpecFileName,
InboundPort: options.inboundPort,
OutboundPort: options.outboundPort,
IgnoreInboundPorts: strings.Join(ignoreInboundPorts, ","),
IgnoreOutboundPorts: strings.Join(ignoreOutboundPorts, ","),
ProxyAutoInjectEnabled: options.proxyAutoInject,
ProxyAutoInjectLabel: k8s.ProxyAutoInjectLabel,
ProxyUID: options.proxyUID,
ProxyMetricsPort: options.proxyMetricsPort,
ProxyControlPort: options.proxyControlPort,
ProxyInjectorTLSSecret: k8s.ProxyInjectorTLSSecret,
ProxyInjectorSidecarConfig: k8s.ProxyInjectorSidecarConfig,
ProxySpecFileName: k8s.ProxySpecFileName,
ProxyInitSpecFileName: k8s.ProxyInitSpecFileName,
ProxyInitImage: options.taggedProxyInitImage(),
ProxyImage: options.taggedProxyImage(),
ProxyResourceRequestCPU: options.proxyCpuRequest,
ProxyResourceRequestMemory: options.proxyMemoryRequest,
ProxyBindTimeout: "1m",
}, nil
}
@ -127,7 +190,19 @@ func render(config installConfig, w io.Writer, options *installOptions) error {
if err != nil {
return err
}
if config.ProxyAutoInjectEnabled {
proxyInjectorTemplate, err := template.New("linkerd").Parse(install.ProxyInjectorTemplate)
if err != nil {
return err
}
err = proxyInjectorTemplate.Execute(buf, config)
if err != nil {
return err
}
}
}
injectOptions := newInjectOptions()
injectOptions.proxyConfigOptions = options.proxyConfigOptions

View File

@ -21,24 +21,47 @@ func TestRender(t *testing.T) {
// A configuration that shows that all config setting strings are honored
// by `render()`.
metaConfig := installConfig{
Namespace: "Namespace",
ControllerImage: "ControllerImage",
WebImage: "WebImage",
PrometheusImage: "PrometheusImage",
GrafanaImage: "GrafanaImage",
ControllerReplicas: 1,
WebReplicas: 2,
PrometheusReplicas: 3,
ImagePullPolicy: "ImagePullPolicy",
UUID: "UUID",
CliVersion: "CliVersion",
ControllerLogLevel: "ControllerLogLevel",
ControllerComponentLabel: "ControllerComponentLabel",
CreatedByAnnotation: "CreatedByAnnotation",
ProxyAPIPort: 123,
EnableTLS: true,
TLSTrustAnchorConfigMapName: "TLSTrustAnchorConfigMapName",
ProxyContainerName: "ProxyContainerName",
Namespace: "Namespace",
ControllerImage: "ControllerImage",
WebImage: "WebImage",
PrometheusImage: "PrometheusImage",
GrafanaImage: "GrafanaImage",
ControllerReplicas: 1,
WebReplicas: 2,
PrometheusReplicas: 3,
ImagePullPolicy: "ImagePullPolicy",
UUID: "UUID",
CliVersion: "CliVersion",
ControllerLogLevel: "ControllerLogLevel",
ControllerComponentLabel: "ControllerComponentLabel",
CreatedByAnnotation: "CreatedByAnnotation",
ProxyAPIPort: 123,
EnableTLS: true,
TLSTrustAnchorConfigMapName: "TLSTrustAnchorConfigMapName",
ProxyContainerName: "ProxyContainerName",
TLSTrustAnchorFileName: "TLSTrustAnchorFileName",
TLSCertFileName: "TLSCertFileName",
TLSPrivateKeyFileName: "TLSPrivateKeyFileName",
TLSTrustAnchorVolumeSpecFileName: "TLSTrustAnchorVolumeSpecFileName",
TLSIdentityVolumeSpecFileName: "TLSIdentityVolumeSpecFileName",
ProxyAutoInjectEnabled: true,
ProxyAutoInjectLabel: "ProxyAutoInjectLabel",
ProxyUID: 2102,
InboundPort: 4143,
OutboundPort: 4140,
ProxyControlPort: 4190,
ProxyMetricsPort: 4191,
ProxyInitImage: "ProxyInitImage",
ProxyImage: "ProxyImage",
ProxyInjectorTLSSecret: "ProxyInjectorTLSSecret",
ProxyInjectorSidecarConfig: "ProxyInjectorSidecarConfig",
ProxySpecFileName: "ProxySpecFileName",
ProxyInitSpecFileName: "ProxyInitSpecFileName",
IgnoreInboundPorts: "4190,4191,1,2,3",
IgnoreOutboundPorts: "2,3,4",
ProxyResourceRequestCPU: "RequestCPU",
ProxyResourceRequestMemory: "RequestMemory",
ProxyBindTimeout: "1m",
}
testCases := []struct {

View File

@ -135,6 +135,10 @@ type proxyConfigOptions struct {
initImage string
dockerRegistry string
imagePullPolicy string
inboundPort uint
outboundPort uint
ignoreInboundPorts []uint
ignoreOutboundPorts []uint
proxyUID int64
proxyLogLevel string
proxyBindTimeout string
@ -159,6 +163,10 @@ func newProxyConfigOptions() *proxyConfigOptions {
initImage: defaultDockerRegistry + "/proxy-init",
dockerRegistry: defaultDockerRegistry,
imagePullPolicy: "IfNotPresent",
inboundPort: 4143,
outboundPort: 4140,
ignoreInboundPorts: nil,
ignoreOutboundPorts: nil,
proxyUID: 2102,
proxyLogLevel: "warn,linkerd2_proxy=info",
proxyBindTimeout: "10s",
@ -231,11 +239,14 @@ func addProxyConfigFlags(cmd *cobra.Command, options *proxyConfigOptions) {
cmd.PersistentFlags().Int64Var(&options.proxyUID, "proxy-uid", options.proxyUID, "Run the proxy under this user ID")
cmd.PersistentFlags().StringVar(&options.proxyLogLevel, "proxy-log-level", options.proxyLogLevel, "Log level for the proxy")
cmd.PersistentFlags().StringVar(&options.proxyBindTimeout, "proxy-bind-timeout", options.proxyBindTimeout, "Timeout the proxy will use")
cmd.PersistentFlags().UintVar(&options.inboundPort, "inbound-port", options.inboundPort, "Proxy port to use for inbound traffic")
cmd.PersistentFlags().UintVar(&options.outboundPort, "outbound-port", options.outboundPort, "Proxy port to use for outbound traffic")
cmd.PersistentFlags().UintVar(&options.proxyAPIPort, "api-port", options.proxyAPIPort, "Port where the Linkerd controller is running")
cmd.PersistentFlags().UintVar(&options.proxyControlPort, "control-port", options.proxyControlPort, "Proxy port to use for control")
cmd.PersistentFlags().UintVar(&options.proxyMetricsPort, "metrics-port", options.proxyMetricsPort, "Proxy port to serve metrics on")
cmd.PersistentFlags().StringVar(&options.tls, "tls", options.tls, "Enable TLS; valid settings: \"optional\"")
cmd.PersistentFlags().StringVar(&options.proxyCpuRequest, "proxy-cpu", options.proxyCpuRequest, "Amount of CPU units that the proxy sidecar requests")
cmd.PersistentFlags().StringVar(&options.proxyMemoryRequest, "proxy-memory", options.proxyMemoryRequest, "Amount of Memory that the proxy sidecar requests")
cmd.PersistentFlags().UintSliceVar(&options.ignoreInboundPorts, "skip-inbound-ports", options.ignoreInboundPorts, "Ports that should skip the proxy and send directly to the application")
cmd.PersistentFlags().UintSliceVar(&options.ignoreOutboundPorts, "skip-outbound-ports", options.ignoreOutboundPorts, "Outbound ports that should skip the proxy")
}

View File

@ -3,6 +3,8 @@ kind: Namespace
apiVersion: v1
metadata:
name: Namespace
labels:
ProxyAutoInjectLabel: disabled
### Service Account Controller ###
---
@ -887,6 +889,9 @@ rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "update"]
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"]
verbs: ["list", "get", "watch"]
---
kind: ClusterRoleBinding
@ -933,6 +938,7 @@ spec:
- args:
- ca
- -controller-namespace=Namespace
- -proxy-auto-inject=true
- -log-level=ControllerLogLevel
image: ControllerImage
imagePullPolicy: ImagePullPolicy
@ -1015,3 +1021,295 @@ spec:
serviceAccount: linkerd-ca
status: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
CreatedByAnnotation: CliVersion
creationTimestamp: null
labels:
ControllerComponentLabel: proxy-injector
name: proxy-injector
namespace: Namespace
spec:
replicas: 1
selector:
matchLabels:
ControllerComponentLabel: proxy-injector
strategy: {}
template:
metadata:
annotations:
CreatedByAnnotation: CliVersion
linkerd.io/created-by: linkerd/cli undefined
linkerd.io/proxy-version: undefined
creationTimestamp: null
labels:
ControllerComponentLabel: proxy-injector
linkerd.io/control-plane-ns: Namespace
linkerd.io/proxy-deployment: proxy-injector
spec:
containers:
- args:
- proxy-injector
- -controller-namespace=Namespace
- -log-level=ControllerLogLevel
image: ControllerImage
imagePullPolicy: ImagePullPolicy
livenessProbe:
httpGet:
path: /ping
port: 9995
initialDelaySeconds: 10
name: proxy-injector
ports:
- containerPort: 443
name: proxy-injector
readinessProbe:
failureThreshold: 7
httpGet:
path: /ready
port: 9995
resources: {}
volumeMounts:
- mountPath: /var/linkerd-io/trust-anchors
name: linkerd-trust-anchors
readOnly: true
- mountPath: /var/linkerd-io/identity
name: webhook-secrets
readOnly: true
- mountPath: /var/linkerd-io/config
name: proxy-spec
- env:
- name: LINKERD2_PROXY_LOG
value: warn,linkerd2_proxy=info
- name: LINKERD2_PROXY_BIND_TIMEOUT
value: 10s
- name: LINKERD2_PROXY_CONTROL_URL
value: tcp://proxy-api.Namespace.svc.cluster.local:8086
- name: LINKERD2_PROXY_CONTROL_LISTENER
value: tcp://0.0.0.0:4190
- name: LINKERD2_PROXY_METRICS_LISTENER
value: tcp://0.0.0.0:4191
- name: LINKERD2_PROXY_OUTBOUND_LISTENER
value: tcp://127.0.0.1:4140
- name: LINKERD2_PROXY_INBOUND_LISTENER
value: tcp://0.0.0.0:4143
- name: LINKERD2_PROXY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: gcr.io/linkerd-io/proxy:undefined
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
name: linkerd-proxy
ports:
- containerPort: 4143
name: linkerd-proxy
- containerPort: 4191
name: linkerd-metrics
readinessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
resources: {}
securityContext:
runAsUser: 2102
terminationMessagePolicy: FallbackToLogsOnError
initContainers:
- args:
- --incoming-proxy-port
- "4143"
- --outgoing-proxy-port
- "4140"
- --proxy-uid
- "2102"
- --inbound-ports-to-ignore
- 4190,4191
image: gcr.io/linkerd-io/proxy-init:undefined
imagePullPolicy: IfNotPresent
name: linkerd-init
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: false
terminationMessagePolicy: FallbackToLogsOnError
serviceAccount: linkerd-proxy-injector
volumes:
- name: webhook-secrets
secret:
optional: true
secretName: ProxyInjectorTLSSecret
- configMap:
name: ProxyInjectorSidecarConfig
name: proxy-spec
status: {}
---
### Proxy Injector Service Account ###
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-proxy-injector
namespace: Namespace
---
### Proxy Injector RBAC ###
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-Namespace-proxy-injector
rules:
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"]
verbs: ["create", "update", "get", "watch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-Namespace-proxy-injector
subjects:
- kind: ServiceAccount
name: linkerd-proxy-injector
namespace: Namespace
apiGroup: ""
roleRef:
kind: ClusterRole
name: linkerd-Namespace-proxy-injector
apiGroup: rbac.authorization.k8s.io
---
### Proxy Injector Service ###
kind: Service
apiVersion: v1
metadata:
name: proxy-injector
namespace: Namespace
labels:
ControllerComponentLabel: proxy-injector
annotations:
CreatedByAnnotation: CliVersion
spec:
type: ClusterIP
selector:
ControllerComponentLabel: proxy-injector
ports:
- name: proxy-injector
port: 443
targetPort: proxy-injector
---
### Proxy Sidecar Container Spec ###
kind: ConfigMap
apiVersion: v1
metadata:
name: ProxyInjectorSidecarConfig
namespace: Namespace
labels:
ControllerComponentLabel: proxy-injector
annotations:
CreatedByAnnotation: CliVersion
data:
ProxyInitSpecFileName: |
args:
- --incoming-proxy-port
- 4143
- --outgoing-proxy-port
- 4140
- --proxy-uid
- 2102
- --inbound-ports-to-ignore
- 4190,4191,1,2,3
- --outbound-ports-to-ignore
- 2,3,4
image: ProxyInitImage
imagePullPolicy: IfNotPresent
name: linkerd-init
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: false
terminationMessagePolicy: FallbackToLogsOnError
ProxySpecFileName: |
env:
- name: LINKERD2_PROXY_LOG
value: warn,linkerd2_proxy=info
- name: LINKERD2_PROXY_BIND_TIMEOUT
value: 1m
- name: LINKERD2_PROXY_CONTROL_URL
value: tcp://proxy-api.Namespace.svc.cluster.local:123
- name: LINKERD2_PROXY_CONTROL_LISTENER
value: tcp://0.0.0.0:4190
- name: LINKERD2_PROXY_METRICS_LISTENER
value: tcp://0.0.0.0:4191
- name: LINKERD2_PROXY_OUTBOUND_LISTENER
value: tcp://127.0.0.1:4140
- name: LINKERD2_PROXY_INBOUND_LISTENER
value: tcp://0.0.0.0:4143
- name: LINKERD2_PROXY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LINKERD2_PROXY_TLS_TRUST_ANCHORS
value: /var/linkerd-io/trust-anchors/TLSTrustAnchorFileName
- name: LINKERD2_PROXY_TLS_CERT
value: /var/linkerd-io/identity/TLSCertFileName
- name: LINKERD2_PROXY_TLS_PRIVATE_KEY
value: /var/linkerd-io/identity/TLSPrivateKeyFileName
- name: LINKERD2_PROXY_TLS_POD_IDENTITY
value: "" # this value will be computed by the webhook
- name: LINKERD2_PROXY_CONTROLLER_NAMESPACE
value: Namespace
- name: LINKERD2_PROXY_TLS_CONTROLLER_IDENTITY
value: "" # this value will be computed by the webhook
image: ProxyImage
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
name: linkerd-proxy
ports:
- containerPort: 4143
name: linkerd-proxy
- containerPort: 4191
name: linkerd-metrics
readinessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
resources:
requests:
cpu: RequestCPU
memory: RequestMemory
securityContext:
runAsUser: 2102
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- mountPath: /var/linkerd-io/trust-anchors
name: linkerd-trust-anchors
readOnly: true
- mountPath: /var/linkerd-io/identity
name: linkerd-secrets
readOnly: true
TLSTrustAnchorVolumeSpecFileName: |
name: linkerd-trust-anchors
configMap:
name: TLSTrustAnchorConfigMapName
optional: true
TLSIdentityVolumeSpecFileName: |
name: linkerd-secrets
secret:
secretName: "" # this value will be computed by the webhook
optional: true
---

View File

@ -6,6 +6,10 @@ kind: Namespace
apiVersion: v1
metadata:
name: {{.Namespace}}
{{- if and .EnableTLS .ProxyAutoInjectEnabled }}
labels:
{{.ProxyAutoInjectLabel}}: disabled
{{- end }}
### Service Account Controller ###
---
@ -613,6 +617,11 @@ rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "update"]
{{- if and .EnableTLS .ProxyAutoInjectEnabled }}
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"]
verbs: ["list", "get", "watch"]
{{- end }}
---
kind: ClusterRoleBinding
@ -659,6 +668,9 @@ spec:
args:
- "ca"
- "-controller-namespace={{.Namespace}}"
{{- if and .EnableTLS .ProxyAutoInjectEnabled }}
- "-proxy-auto-inject={{ .ProxyAutoInjectEnabled }}"
{{- end }}
- "-log-level={{.ControllerLogLevel}}"
livenessProbe:
httpGet:
@ -671,3 +683,240 @@ spec:
port: 9997
failureThreshold: 7
`
const ProxyInjectorTemplate = `
---
### Proxy Injector Deployment ###
kind: Deployment
apiVersion: apps/v1
metadata:
name: proxy-injector
namespace: {{.Namespace}}
labels:
{{.ControllerComponentLabel}}: proxy-injector
annotations:
{{.CreatedByAnnotation}}: {{.CliVersion}}
spec:
replicas: {{.ControllerReplicas}}
selector:
matchLabels:
{{.ControllerComponentLabel}}: proxy-injector
template:
metadata:
labels:
{{.ControllerComponentLabel}}: proxy-injector
annotations:
{{.CreatedByAnnotation}}: {{.CliVersion}}
spec:
serviceAccount: linkerd-proxy-injector
containers:
- name: proxy-injector
image: {{.ControllerImage}}
imagePullPolicy: {{.ImagePullPolicy}}
args:
- "proxy-injector"
- "-controller-namespace={{.Namespace}}"
- "-log-level={{.ControllerLogLevel}}"
ports:
- name: proxy-injector
containerPort: 443
volumeMounts:
- name: linkerd-trust-anchors
mountPath: /var/linkerd-io/trust-anchors
readOnly: true
- name: webhook-secrets
mountPath: /var/linkerd-io/identity
readOnly: true
- name: proxy-spec
mountPath: /var/linkerd-io/config
livenessProbe:
httpGet:
path: /ping
port: 9995
initialDelaySeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 9995
failureThreshold: 7
volumes:
- name: webhook-secrets
secret:
secretName: {{.ProxyInjectorTLSSecret}}
optional: true
- name: proxy-spec
configMap:
name: {{.ProxyInjectorSidecarConfig}}
---
### Proxy Injector Service Account ###
kind: ServiceAccount
apiVersion: v1
metadata:
name: linkerd-proxy-injector
namespace: {{.Namespace}}
---
### Proxy Injector RBAC ###
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-{{.Namespace}}-proxy-injector
rules:
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"]
verbs: ["create", "update", "get", "watch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: linkerd-{{.Namespace}}-proxy-injector
subjects:
- kind: ServiceAccount
name: linkerd-proxy-injector
namespace: {{.Namespace}}
apiGroup: ""
roleRef:
kind: ClusterRole
name: linkerd-{{.Namespace}}-proxy-injector
apiGroup: rbac.authorization.k8s.io
---
### Proxy Injector Service ###
kind: Service
apiVersion: v1
metadata:
name: proxy-injector
namespace: {{.Namespace}}
labels:
{{.ControllerComponentLabel}}: proxy-injector
annotations:
{{.CreatedByAnnotation}}: {{.CliVersion}}
spec:
type: ClusterIP
selector:
{{.ControllerComponentLabel}}: proxy-injector
ports:
- name: proxy-injector
port: 443
targetPort: proxy-injector
---
### Proxy Sidecar Container Spec ###
kind: ConfigMap
apiVersion: v1
metadata:
name: {{.ProxyInjectorSidecarConfig}}
namespace: {{.Namespace}}
labels:
{{.ControllerComponentLabel}}: proxy-injector
annotations:
{{.CreatedByAnnotation}}: {{.CliVersion}}
data:
{{.ProxyInitSpecFileName}}: |
args:
- --incoming-proxy-port
- {{.InboundPort}}
- --outgoing-proxy-port
- {{.OutboundPort}}
- --proxy-uid
- {{.ProxyUID}}
{{- if ne (len .IgnoreInboundPorts) 0}}
- --inbound-ports-to-ignore
- {{.IgnoreInboundPorts}}
{{- end }}
{{- if ne (len .IgnoreOutboundPorts) 0}}
- --outbound-ports-to-ignore
- {{.IgnoreOutboundPorts}}
{{- end}}
image: {{.ProxyInitImage}}
imagePullPolicy: IfNotPresent
name: linkerd-init
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: false
terminationMessagePolicy: FallbackToLogsOnError
{{.ProxySpecFileName}}: |
env:
- name: LINKERD2_PROXY_LOG
value: warn,linkerd2_proxy=info
- name: LINKERD2_PROXY_BIND_TIMEOUT
value: {{.ProxyBindTimeout}}
- name: LINKERD2_PROXY_CONTROL_URL
value: tcp://proxy-api.{{.Namespace}}.svc.cluster.local:{{.ProxyAPIPort}}
- name: LINKERD2_PROXY_CONTROL_LISTENER
value: tcp://0.0.0.0:{{.ProxyControlPort}}
- name: LINKERD2_PROXY_METRICS_LISTENER
value: tcp://0.0.0.0:{{.ProxyMetricsPort}}
- name: LINKERD2_PROXY_OUTBOUND_LISTENER
value: tcp://127.0.0.1:{{.OutboundPort}}
- name: LINKERD2_PROXY_INBOUND_LISTENER
value: tcp://0.0.0.0:{{.InboundPort}}
- name: LINKERD2_PROXY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LINKERD2_PROXY_TLS_TRUST_ANCHORS
value: /var/linkerd-io/trust-anchors/{{.TLSTrustAnchorFileName}}
- name: LINKERD2_PROXY_TLS_CERT
value: /var/linkerd-io/identity/{{.TLSCertFileName}}
- name: LINKERD2_PROXY_TLS_PRIVATE_KEY
value: /var/linkerd-io/identity/{{.TLSPrivateKeyFileName}}
- name: LINKERD2_PROXY_TLS_POD_IDENTITY
value: "" # this value will be computed by the webhook
- name: LINKERD2_PROXY_CONTROLLER_NAMESPACE
value: {{.Namespace}}
- name: LINKERD2_PROXY_TLS_CONTROLLER_IDENTITY
value: "" # this value will be computed by the webhook
image: {{.ProxyImage}}
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /metrics
port: {{.ProxyMetricsPort}}
initialDelaySeconds: 10
name: linkerd-proxy
ports:
- containerPort: {{.InboundPort}}
name: linkerd-proxy
- containerPort: {{.ProxyMetricsPort}}
name: linkerd-metrics
readinessProbe:
httpGet:
path: /metrics
port: {{.ProxyMetricsPort}}
initialDelaySeconds: 10
{{- if or .ProxyResourceRequestCPU .ProxyResourceRequestMemory }}
resources:
requests:
{{- if .ProxyResourceRequestCPU }}
cpu: {{.ProxyResourceRequestCPU}}
{{- end }}
{{- if .ProxyResourceRequestMemory}}
memory: {{.ProxyResourceRequestMemory}}
{{- end }}
{{- end }}
securityContext:
runAsUser: {{.ProxyUID}}
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- mountPath: /var/linkerd-io/trust-anchors
name: linkerd-trust-anchors
readOnly: true
- mountPath: /var/linkerd-io/identity
name: linkerd-secrets
readOnly: true
{{.TLSTrustAnchorVolumeSpecFileName}}: |
name: linkerd-trust-anchors
configMap:
name: {{.TLSTrustAnchorConfigMapName}}
optional: true
{{.TLSIdentityVolumeSpecFileName}}: |
name: linkerd-secrets
secret:
secretName: "" # this value will be computed by the webhook
optional: true
`

View File

@ -1,5 +1,5 @@
## compile controller services
FROM gcr.io/linkerd-io/go-deps:64a32a2a as golang
FROM gcr.io/linkerd-io/go-deps:0c590d0e as golang
WORKDIR /go/src/github.com/linkerd/linkerd2
COPY controller/gen controller/gen
COPY pkg pkg

View File

@ -8,6 +8,7 @@ import (
"github.com/linkerd/linkerd2/controller/k8s"
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
"k8s.io/api/admissionregistration/v1beta1"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -31,7 +32,7 @@ type CertificateController struct {
queue workqueue.RateLimitingInterface
}
func NewCertificateController(controllerNamespace string, k8sAPI *k8s.API) (*CertificateController, error) {
func NewCertificateController(controllerNamespace string, k8sAPI *k8s.API, proxyAutoInject bool) (*CertificateController, error) {
ca, err := NewCA()
if err != nil {
return nil, err
@ -52,6 +53,15 @@ func NewCertificateController(controllerNamespace string, k8sAPI *k8s.API) (*Cer
},
)
if proxyAutoInject {
k8sAPI.MWC().Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: c.handleMWCAdd,
UpdateFunc: c.handleMWCUpdate,
},
)
}
c.syncHandler = c.syncObject
return c, nil
@ -134,6 +144,7 @@ func (c *CertificateController) syncSecret(key string) error {
Namespace: parts[2],
ControllerNamespace: c.namespace,
}
dnsName := identity.ToDNSName()
secretName := identity.ToSecretName()
certAndPrivateKey, err := c.ca.IssueEndEntityCertificate(dnsName)
@ -172,3 +183,17 @@ func (c *CertificateController) handlePodAdd(obj interface{}) {
func (c *CertificateController) handlePodUpdate(oldObj, newObj interface{}) {
c.handlePodAdd(newObj)
}
func (c *CertificateController) handleMWCAdd(obj interface{}) {
mwc := obj.(*v1beta1.MutatingWebhookConfiguration)
log.Debugf("enqueuing secret write for mutating webhook configuration %q", mwc.ObjectMeta.Name)
for _, webhook := range mwc.Webhooks {
if mwc.Name == pkgK8s.ProxyInjectorWebhookConfig {
c.queue.Add(fmt.Sprintf("%s.%s.%s", webhook.ClientConfig.Service.Name, pkgK8s.Service, webhook.ClientConfig.Service.Namespace))
}
}
}
func (c *CertificateController) handleMWCUpdate(oldObj, newObj interface{}) {
c.handleMWCAdd(newObj)
}

View File

@ -67,7 +67,7 @@ func new(fixtures ...string) (*CertificateController, chan bool, chan struct{},
return nil, nil, nil, fmt.Errorf("NewFakeAPI returned an error: %s", err)
}
controller, err := NewCertificateController(controllerNS, k8sAPI)
controller, err := NewCertificateController(controllerNS, k8sAPI, false)
if err != nil {
return nil, nil, nil, fmt.Errorf("NewCertificateController returned an error: %s", err)
}

View File

@ -17,6 +17,7 @@ func main() {
metricsAddr := flag.String("metrics-addr", ":9997", "address to serve scrapable metrics on")
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
kubeConfigPath := flag.String("kubeconfig", "", "path to kube config")
proxyAutoInject := flag.Bool("proxy-auto-inject", false, "if true, watch for the add and update events of mutating webhook configurations")
flags.ConfigureAndParse()
stop := make(chan os.Signal, 1)
@ -26,13 +27,15 @@ func main() {
if err != nil {
log.Fatal(err.Error())
}
k8sAPI := k8s.NewAPI(
k8sClient,
k8s.Pod,
k8s.RS,
)
controller, err := ca.NewCertificateController(*controllerNamespace, k8sAPI)
var k8sAPI *k8s.API
if *proxyAutoInject {
k8sAPI = k8s.NewAPI(k8sClient, k8s.Pod, k8s.RS, k8s.MWC)
} else {
k8sAPI = k8s.NewAPI(k8sClient, k8s.Pod, k8s.RS)
}
controller, err := ca.NewCertificateController(*controllerNamespace, k8sAPI, *proxyAutoInject)
if err != nil {
log.Fatalf("Failed to create CertificateController: %v", err)
}

View File

@ -0,0 +1,115 @@
package main
import (
"context"
"flag"
"net/http"
"os"
"os/signal"
"time"
"github.com/linkerd/linkerd2/controller/k8s"
"github.com/linkerd/linkerd2/controller/proxy-injector"
"github.com/linkerd/linkerd2/pkg/admin"
"github.com/linkerd/linkerd2/pkg/flags"
k8sPkg "github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/wait"
)
func main() {
metricsAddr := flag.String("metrics-addr", ":9995", "address to serve scrapable metrics on")
addr := flag.String("addr", ":443", "address to serve on")
kubeconfig := flag.String("kubeconfig", "", "path to kubeconfig")
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
volumeMountsWaitTime := flag.Duration("volume-mounts-wait", 3*time.Minute, "maximum wait time for the secret volumes to mount before the timeout expires")
webhookServiceName := flag.String("webhook-service", "proxy-injector.linkerd.io", "name of the admission webhook")
flags.ConfigureAndParse()
stop := make(chan os.Signal, 1)
defer close(stop)
signal.Notify(stop, os.Interrupt, os.Kill)
k8sClient, err := k8s.NewClientSet(*kubeconfig)
if err != nil {
log.Fatal("failed to initialize Kubernetes client: ", err)
}
log.Infof("waiting for the trust anchors volume to mount at %s", k8sPkg.MountPathTLSTrustAnchor)
if err := waitForMounts(*volumeMountsWaitTime, k8sPkg.MountPathTLSTrustAnchor); err != context.Canceled {
log.Fatalf("failed to mount the ca bundle: %s", err)
}
webhookConfig, err := injector.NewWebhookConfig(k8sClient, *controllerNamespace, *webhookServiceName, k8sPkg.MountPathTLSTrustAnchor)
if err != nil {
log.Fatalf("failed to read the trust anchor file: %s", err)
}
mwc, err := webhookConfig.CreateOrUpdate()
if err != nil {
log.Fatalf("failed to create the mutating webhook configurations resource: ", err)
}
log.Info("created or updated mutating webhook configuration: ", mwc.ObjectMeta.SelfLink)
var (
certFile = k8sPkg.MountPathTLSIdentityCert
keyFile = k8sPkg.MountPathTLSIdentityKey
)
log.Infof("waiting for the tls secrets to mount at %s and %s", certFile, keyFile)
if err := waitForMounts(*volumeMountsWaitTime, certFile, keyFile); err != context.Canceled {
log.Fatalf("failed to mount the tls secrets: %s", err)
}
resources := &injector.WebhookResources{
FileProxySpec: k8sPkg.MountPathConfigProxySpec,
FileProxyInitSpec: k8sPkg.MountPathConfigProxyInitSpec,
FileTLSTrustAnchorVolumeSpec: k8sPkg.MountPathTLSTrustAnchorVolumeSpec,
FileTLSIdentityVolumeSpec: k8sPkg.MountPathTLSIdentityVolumeSpec,
}
s, err := injector.NewWebhookServer(k8sClient, resources, *addr, *controllerNamespace, certFile, keyFile)
if err != nil {
log.Fatal("failed to initialize the webhook server: ", err)
}
go func() {
log.Infof("listening at %s", *addr)
if err := s.ListenAndServeTLS("", ""); err != nil {
if err == http.ErrServerClosed {
return
}
log.Fatal(err)
}
}()
go admin.StartServer(*metricsAddr, nil)
<-stop
log.Info("shutting down webhook server")
if err := s.Shutdown(); err != nil {
log.Error(err)
}
}
func waitForMounts(timeout time.Duration, paths ...string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
wait.Until(func() {
ready := 0
for _, file := range paths {
if _, err := os.Stat(file); err != nil {
log.Infof("mount not ready: %s", file)
return
}
ready += 1
log.Infof("mount ready: %s", file)
if ready == len(paths) {
break
}
}
cancel()
}, time.Millisecond*500, ctx.Done())
return ctx.Err()
}

View File

@ -15,6 +15,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
arinformers "k8s.io/client-go/informers/admissionregistration/v1beta1"
appinformers "k8s.io/client-go/informers/apps/v1beta2"
coreinformers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
@ -32,6 +33,7 @@ const (
RC
RS
Svc
MWC // mutating webhook configuration
)
// API provides shared informers for all Kubernetes objects
@ -46,6 +48,7 @@ type API struct {
rc coreinformers.ReplicationControllerInformer
rs appinformers.ReplicaSetInformer
svc coreinformers.ServiceInformer
mwc arinformers.MutatingWebhookConfigurationInformer
syncChecks []cache.InformerSynced
sharedInformers informers.SharedInformerFactory
@ -87,6 +90,9 @@ func NewAPI(k8sClient kubernetes.Interface, resources ...ApiResource) *API {
case Svc:
api.svc = sharedInformers.Core().V1().Services()
api.syncChecks = append(api.syncChecks, api.svc.Informer().HasSynced)
case MWC:
api.mwc = sharedInformers.Admissionregistration().V1beta1().MutatingWebhookConfigurations()
api.syncChecks = append(api.syncChecks, api.mwc.Informer().HasSynced)
}
}
@ -169,6 +175,13 @@ func (api *API) CM() coreinformers.ConfigMapInformer {
return api.cm
}
func (api *API) MWC() arinformers.MutatingWebhookConfigurationInformer {
if api.mwc == nil {
panic("MWC informer not configured")
}
return api.mwc
}
// GetObjects returns a list of Kubernetes objects, given a namespace, type, and name.
// If namespace is an empty string, match objects in all namespaces.
// If name is an empty string, match all objects of the given type.

View File

@ -33,5 +33,6 @@ func NewFakeAPI(configs ...string) (*API, error) {
RC,
RS,
Svc,
MWC,
), nil
}

View File

@ -0,0 +1,17 @@
package fake
import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
)
// FakeClient is a fake clientset that implements the kubernetes.Interface.
type Client struct {
kubernetes.Interface
}
// NewClient returns a fake Kubernetes clientset.
func NewClient(kubeconfig string) (kubernetes.Interface, error) {
client := fake.NewSimpleClientset()
return &Client{client}, nil
}

View File

@ -0,0 +1,4 @@
name: linkerd-secrets
secret:
secretName: ""
optional: true

View File

@ -0,0 +1,4 @@
name: linkerd-trust-anchors
configMap:
name: linkerd-ca-bundle
optional: true

View File

@ -0,0 +1,19 @@
args:
- --incoming-proxy-port
- 4143
- --outgoing-proxy-port
- 4140
- --proxy-uid
- 2102
- --inbound-ports-to-ignore
- 4190,4191
image: gcr.io/linkerd-io/proxy-init:v18.8.4
imagePullPolicy: IfNotPresent
name: linkerd-init
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: false
terminationMessagePolicy: FallbackToLogsOnError

View File

@ -0,0 +1,60 @@
env:
- name: LINKERD2_PROXY_LOG
value: warn,linkerd2_proxy=info
- name: LINKERD2_PROXY_BIND_TIMEOUT
value: 10s
- name: LINKERD2_PROXY_CONTROL_URL
value: tcp://proxy-api.linkerd.svc.cluster.local:8086
- name: LINKERD2_PROXY_CONTROL_LISTENER
value: tcp://0.0.0.0:4190
- name: LINKERD2_PROXY_METRICS_LISTENER
value: tcp://0.0.0.0:4191
- name: LINKERD2_PROXY_OUTBOUND_LISTENER
value: tcp://127.0.0.1:4140
- name: LINKERD2_PROXY_INBOUND_LISTENER
value: tcp://0.0.0.0:4143
- name: LINKERD2_PROXY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LINKERD2_PROXY_TLS_TRUST_ANCHORS
value: /var/linkerd-io/trust-anchors/trust-anchors.pem
- name: LINKERD2_PROXY_TLS_CERT
value: /var/linkerd-io/identity/certificate.crt
- name: LINKERD2_PROXY_TLS_PRIVATE_KEY
value: /var/linkerd-io/identity/private-key.p8
- name: LINKERD2_PROXY_TLS_POD_IDENTITY
value: ""
- name: LINKERD2_PROXY_CONTROLLER_NAMESPACE
value: linkerd
- name: LINKERD2_PROXY_TLS_CONTROLLER_IDENTITY
value: ""
image: gcr.io/linkerd-io/proxy:v18.8.4
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
name: linkerd-proxy
ports:
- containerPort: 4143
name: linkerd-proxy
- containerPort: 4191
name: linkerd-metrics
readinessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
resources: {}
securityContext:
runAsUser: 2102
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- mountPath: /var/linkerd-io/trust-anchors
name: linkerd-trust-anchors
readOnly: true
- mountPath: /var/linkerd-io/identity
name: linkerd-secrets
readOnly: true

View File

@ -0,0 +1,26 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx
namespace: kube-public
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
linkerd.io/auto-inject: completed
annotations:
created-by: isim
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80

View File

@ -0,0 +1,26 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx
namespace: kube-public
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
linkerd.io/auto-inject: disabled
annotations:
created-by: isim
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80

View File

@ -0,0 +1,25 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx
namespace: kube-public
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
annotations:
created-by: isim
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80

View File

@ -0,0 +1,26 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx
namespace: kube-public
labels:
app: nginx
linkerd.io/auto-inject: enabled
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
annotations:
created-by: isim
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80

View File

@ -0,0 +1,30 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx
namespace: kube-public
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
annotations:
created-by: isim
spec:
initContainers:
- name: linkerd-init
image: gcr.io/linkerd-io/proxy-init
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80
- name: linkerd-proxy
image: gc.io/linkerd-io/proxy

View File

@ -0,0 +1,95 @@
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1beta1",
"request": {
"uid": "3c3c45ff-bee9-11e8-9c41-b4d755961931",
"kind": {
"group": "apps",
"version": "v1",
"kind": "Deployment"
},
"resource": {
"group": "apps",
"version": "v1",
"resource": "deployments"
},
"namespace": "kube-public",
"operation": "CREATE",
"userInfo": {
"username": "minikube-user",
"groups": [
"system:masters",
"system:authenticated"
]
},
"object": {
"metadata": {
"name": "nginx",
"namespace": "kube-public",
"creationTimestamp": null,
"labels": {
"app": "nginx"
},
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"nginx\"},\"name\":\"nginx\",\"namespace\":\"kube-public\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"metadata\":{\"annotations\":{\"created-by\":\"isim\"},\"labels\":{\"app\":\"nginx\"}},\"spec\":{\"containers\":[{\"image\":\"nginx\",\"name\":\"nginx\",\"ports\":[{\"containerPort\":80,\"name\":\"http\"}]}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "nginx",
"linkerd.io/auto-inject": "completed"
},
"annotations": {
"created-by": "isim"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx",
"ports": [
{
"name": "http",
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {}
},
"oldObject": null
}
}

View File

@ -0,0 +1,73 @@
iapiVersion: admission.k8s.io/v1beta1
kind: AdmissionReview
request:
kind:
group: apps
kind: Deployment
version: v1
namespace: kube-public
object:
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"kube-public"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"annotations":{"created-by":"isim"},"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx","name":"nginx","ports":[{"containerPort":80,"name":"http"}]}]}}}}
creationTimestamp: null
labels:
app: nginx
name: nginx
namespace: kube-public
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
created-by: isim
creationTimestamp: null
labels:
app: nginx
linkerd.io/auto-inject: completed
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
name: http
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status: {}
oldObject: null
operation: CREATE
resource:
group: apps
resource: deployments
version: v1
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931
userInfo:
groups:
- system:masters
- system:authenticated
username: minikube-user
response:
allowed: true
patch:
patchType:
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931

View File

@ -0,0 +1,95 @@
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1beta1",
"request": {
"uid": "3c3c45ff-bee9-11e8-9c41-b4d755961931",
"kind": {
"group": "apps",
"version": "v1",
"kind": "Deployment"
},
"resource": {
"group": "apps",
"version": "v1",
"resource": "deployments"
},
"namespace": "kube-public",
"operation": "CREATE",
"userInfo": {
"username": "minikube-user",
"groups": [
"system:masters",
"system:authenticated"
]
},
"object": {
"metadata": {
"name": "nginx",
"namespace": "kube-public",
"creationTimestamp": null,
"labels": {
"app": "nginx"
},
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"nginx\"},\"name\":\"nginx\",\"namespace\":\"kube-public\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"metadata\":{\"annotations\":{\"created-by\":\"isim\"},\"labels\":{\"app\":\"nginx\"}},\"spec\":{\"containers\":[{\"image\":\"nginx\",\"name\":\"nginx\",\"ports\":[{\"containerPort\":80,\"name\":\"http\"}]}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "nginx",
"linkerd.io/auto-inject": "disabled"
},
"annotations": {
"created-by": "isim"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx",
"ports": [
{
"name": "http",
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {}
},
"oldObject": null
}
}

View File

@ -0,0 +1,73 @@
iapiVersion: admission.k8s.io/v1beta1
kind: AdmissionReview
request:
kind:
group: apps
kind: Deployment
version: v1
namespace: kube-public
object:
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"kube-public"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"annotations":{"created-by":"isim"},"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx","name":"nginx","ports":[{"containerPort":80,"name":"http"}]}]}}}}
creationTimestamp: null
labels:
app: nginx
name: nginx
namespace: kube-public
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
created-by: isim
creationTimestamp: null
labels:
app: nginx
linkerd.io/auto-inject: disabled
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
name: http
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status: {}
oldObject: null
operation: CREATE
resource:
group: apps
resource: deployments
version: v1
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931
userInfo:
groups:
- system:masters
- system:authenticated
username: minikube-user
response:
allowed: true
patch:
patchType:
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931

View File

@ -0,0 +1,95 @@
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1beta1",
"request": {
"uid": "3c3c45ff-bee9-11e8-9c41-b4d755961931",
"kind": {
"group": "apps",
"version": "v1",
"kind": "Deployment"
},
"resource": {
"group": "apps",
"version": "v1",
"resource": "deployments"
},
"namespace": "kube-public",
"operation": "CREATE",
"userInfo": {
"username": "minikube-user",
"groups": [
"system:masters",
"system:authenticated"
]
},
"object": {
"metadata": {
"name": "nginx",
"namespace": "kube-public",
"creationTimestamp": null,
"labels": {
"app": "nginx"
},
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"nginx\"},\"name\":\"nginx\",\"namespace\":\"kube-public\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"metadata\":{\"annotations\":{\"created-by\":\"isim\"},\"labels\":{\"app\":\"nginx\"}},\"spec\":{\"containers\":[{\"image\":\"nginx\",\"name\":\"nginx\",\"ports\":[{\"containerPort\":80,\"name\":\"http\"}]}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "nginx",
"linkerd.io/auto-inject": "enabled"
},
"annotations": {
"created-by": "isim"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx",
"ports": [
{
"name": "http",
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {}
},
"oldObject": null
}
}

View File

@ -0,0 +1,73 @@
iapiVersion: admission.k8s.io/v1beta1
kind: AdmissionReview
request:
kind:
group: apps
kind: Deployment
version: v1
namespace: kube-public
object:
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"kube-public"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"annotations":{"created-by":"isim"},"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx","name":"nginx","ports":[{"containerPort":80,"name":"http"}]}]}}}}
creationTimestamp: null
labels:
app: nginx
name: nginx
namespace: kube-public
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
created-by: isim
creationTimestamp: null
labels:
app: nginx
linkerd.io/auto-inject: enabled
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
name: http
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status: {}
oldObject: null
operation: CREATE
resource:
group: apps
resource: deployments
version: v1
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931
userInfo:
groups:
- system:masters
- system:authenticated
username: minikube-user
response:
allowed: true
patch: W3sib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvdGVtcGxhdGUvc3BlYy9jb250YWluZXJzLzAiLCJ2YWx1ZSI6eyJuYW1lIjoibGlua2VyZC1wcm94eSIsImltYWdlIjoiZ2NyLmlvL2xpbmtlcmQtaW8vcHJveHk6djE4LjguNCIsInBvcnRzIjpbeyJuYW1lIjoibGlua2VyZC1wcm94eSIsImNvbnRhaW5lclBvcnQiOjQxNDN9LHsibmFtZSI6ImxpbmtlcmQtbWV0cmljcyIsImNvbnRhaW5lclBvcnQiOjQxOTF9XSwiZW52IjpbeyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfTE9HIiwidmFsdWUiOiJ3YXJuLGxpbmtlcmQyX3Byb3h5PWluZm8ifSx7Im5hbWUiOiJMSU5LRVJEMl9QUk9YWV9CSU5EX1RJTUVPVVQiLCJ2YWx1ZSI6IjEwcyJ9LHsibmFtZSI6IkxJTktFUkQyX1BST1hZX0NPTlRST0xfVVJMIiwidmFsdWUiOiJ0Y3A6Ly9wcm94eS1hcGkubGlua2VyZC5zdmMuY2x1c3Rlci5sb2NhbDo4MDg2In0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfQ09OVFJPTF9MSVNURU5FUiIsInZhbHVlIjoidGNwOi8vMC4wLjAuMDo0MTkwIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfTUVUUklDU19MSVNURU5FUiIsInZhbHVlIjoidGNwOi8vMC4wLjAuMDo0MTkxIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfT1VUQk9VTkRfTElTVEVORVIiLCJ2YWx1ZSI6InRjcDovLzEyNy4wLjAuMTo0MTQwIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfSU5CT1VORF9MSVNURU5FUiIsInZhbHVlIjoidGNwOi8vMC4wLjAuMDo0MTQzIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfUE9EX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19LHsibmFtZSI6IkxJTktFUkQyX1BST1hZX1RMU19UUlVTVF9BTkNIT1JTIiwidmFsdWUiOiIvdmFyL2xpbmtlcmQtaW8vdHJ1c3QtYW5jaG9ycy90cnVzdC1hbmNob3JzLnBlbSJ9LHsibmFtZSI6IkxJTktFUkQyX1BST1hZX1RMU19DRVJUIiwidmFsdWUiOiIvdmFyL2xpbmtlcmQtaW8vaWRlbnRpdHkvY2VydGlmaWNhdGUuY3J0In0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfVExTX1BSSVZBVEVfS0VZIiwidmFsdWUiOiIvdmFyL2xpbmtlcmQtaW8vaWRlbnRpdHkvcHJpdmF0ZS1rZXkucDgifSx7Im5hbWUiOiJMSU5LRVJEMl9QUk9YWV9UTFNfUE9EX0lERU5USVRZIiwidmFsdWUiOiJuZ2lueC5kZXBsb3ltZW50Lmt1YmUtcHVibGljLmxpbmtlcmQtbWFuYWdlZC5saW5rZXJkLnN2Yy5jbHVzdGVyLmxvY2FsIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfQ09OVFJPTExFUl9OQU1FU1BBQ0UiLCJ2YWx1ZSI6ImxpbmtlcmQifSx7Im5hbWUiOiJMSU5LRVJEMl9QUk9YWV9UTFNfQ09OVFJPTExFUl9JREVOVElUWSIsInZhbHVlIjoiY29udHJvbGxlci5kZXBsb3ltZW50LmxpbmtlcmQubGlua2VyZC1tYW5hZ2VkLmxpbmtlcmQuc3ZjLmNsdXN0ZXIubG9jYWwifV0sInJlc291cmNlcyI6e30sInZvbHVtZU1vdW50cyI6W3sibmFtZSI6ImxpbmtlcmQtdHJ1c3QtYW5jaG9ycyIsInJlYWRPbmx5Ijp0cnVlLCJtb3VudFBhdGgiOiIvdmFyL2xpbmtlcmQtaW8vdHJ1c3QtYW5jaG9ycyJ9LHsibmFtZSI6ImxpbmtlcmQtc2VjcmV0cyIsInJlYWRPbmx5Ijp0cnVlLCJtb3VudFBhdGgiOiIvdmFyL2xpbmtlcmQtaW8vaWRlbnRpdHkifV0sImxpdmVuZXNzUHJvYmUiOnsiaHR0cEdldCI6eyJwYXRoIjoiL21ldHJpY3MiLCJwb3J0Ijo0MTkxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6MTB9LCJyZWFkaW5lc3NQcm9iZSI6eyJodHRwR2V0Ijp7InBhdGgiOiIvbWV0cmljcyIsInBvcnQiOjQxOTF9LCJpbml0aWFsRGVsYXlTZWNvbmRzIjoxMH0sInRlcm1pbmF0aW9uTWVzc2FnZVBvbGljeSI6IkZhbGxiYWNrVG9Mb2dzT25FcnJvciIsImltYWdlUHVsbFBvbGljeSI6IklmTm90UHJlc2VudCIsInNlY3VyaXR5Q29udGV4dCI6eyJydW5Bc1VzZXIiOjIxMDJ9fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9zcGVjL2luaXRDb250YWluZXJzIiwidmFsdWUiOltdfSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL3RlbXBsYXRlL3NwZWMvaW5pdENvbnRhaW5lcnMvMCIsInZhbHVlIjp7Im5hbWUiOiJsaW5rZXJkLWluaXQiLCJpbWFnZSI6Imdjci5pby9saW5rZXJkLWlvL3Byb3h5LWluaXQ6djE4LjguNCIsImFyZ3MiOlsiLS1pbmNvbWluZy1wcm94eS1wb3J0IiwiNDE0MyIsIi0tb3V0Z29pbmctcHJveHktcG9ydCIsIjQxNDAiLCItLXByb3h5LXVpZCIsIjIxMDIiLCItLWluYm91bmQtcG9ydHMtdG8taWdub3JlIiwiNDE5MCw0MTkxIl0sInJlc291cmNlcyI6e30sInRlcm1pbmF0aW9uTWVzc2FnZVBvbGljeSI6IkZhbGxiYWNrVG9Mb2dzT25FcnJvciIsImltYWdlUHVsbFBvbGljeSI6IklmTm90UHJlc2VudCIsInNlY3VyaXR5Q29udGV4dCI6eyJjYXBhYmlsaXRpZXMiOnsiYWRkIjpbIk5FVF9BRE1JTiJdfSwicHJpdmlsZWdlZCI6ZmFsc2V9fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9zcGVjL3ZvbHVtZXMiLCJ2YWx1ZSI6W119LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvdGVtcGxhdGUvc3BlYy92b2x1bWVzLzAiLCJ2YWx1ZSI6eyJuYW1lIjoibGlua2VyZC10cnVzdC1hbmNob3JzIiwiY29uZmlnTWFwIjp7Im5hbWUiOiJsaW5rZXJkLWNhLWJ1bmRsZSIsIm9wdGlvbmFsIjp0cnVlfX19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvdGVtcGxhdGUvc3BlYy92b2x1bWVzLzAiLCJ2YWx1ZSI6eyJuYW1lIjoibGlua2VyZC1zZWNyZXRzIiwic2VjcmV0Ijp7InNlY3JldE5hbWUiOiJuZ2lueC1kZXBsb3ltZW50LXRscy1saW5rZXJkLWlvIiwib3B0aW9uYWwiOnRydWV9fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9tZXRhZGF0YS9sYWJlbHMiLCJ2YWx1ZSI6eyJsaW5rZXJkLmlvL2NvbnRyb2wtcGxhbmUtbnMiOiJsaW5rZXJkIiwibGlua2VyZC5pby9wcm94eS1kZXBsb3ltZW50IjoibmdpbngifX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9tZXRhZGF0YS9hbm5vdGF0aW9ucyIsInZhbHVlIjp7ImxpbmtlcmQuaW8vY3JlYXRlZC1ieSI6ImxpbmtlcmQvcHJveHktaW5qZWN0b3IgdjE4LjguNCIsImxpbmtlcmQuaW8vcHJveHktdmVyc2lvbiI6InYxOC44LjQifX1d
patchType: JSONPatch
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931

View File

@ -0,0 +1,19 @@
args:
- --incoming-proxy-port
- 4143
- --outgoing-proxy-port
- 4140
- --proxy-uid
- 2102
- --inbound-ports-to-ignore
- 4190,4191
image: gcr.io/linkerd-io/proxy-init:v18.8.4
imagePullPolicy: IfNotPresent
name: linkerd-init
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: false
terminationMessagePolicy: FallbackToLogsOnError

View File

@ -0,0 +1,4 @@
name: linkerd-secrets
secret:
secretName: nginx-deployment-tls-linkerd-io
optional: true

View File

@ -0,0 +1,91 @@
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1beta1",
"request": {
"uid": "3c3c45ff-bee9-11e8-9c41-b4d755961931",
"kind": {
"group": "apps",
"version": "v1",
"kind": "Deployment"
},
"resource": {
"group": "apps",
"version": "v1",
"resource": "deployments"
},
"namespace": "kube-public",
"operation": "CREATE",
"userInfo": {
"username": "minikube-user",
"groups": [
"system:masters",
"system:authenticated"
]
},
"object": {
"metadata": {
"name": "nginx",
"namespace": "kube-public",
"creationTimestamp": null,
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"nginx\"},\"name\":\"nginx\",\"namespace\":\"kube-public\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"metadata\":{\"annotations\":{\"created-by\":\"isim\"},\"labels\":{\"app\":\"nginx\"}},\"spec\":{\"containers\":[{\"image\":\"nginx\",\"name\":\"nginx\",\"ports\":[{\"containerPort\":80,\"name\":\"http\"}]}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "nginx"
},
"annotations": {
"created-by": "isim"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx",
"ports": [
{
"name": "http",
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {}
},
"oldObject": null
}
}

View File

@ -0,0 +1,70 @@
iapiVersion: admission.k8s.io/v1beta1
kind: AdmissionReview
request:
kind:
group: apps
kind: Deployment
version: v1
namespace: kube-public
object:
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"kube-public"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"annotations":{"created-by":"isim"},"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx","name":"nginx","ports":[{"containerPort":80,"name":"http"}]}]}}}}
creationTimestamp: null
name: nginx
namespace: kube-public
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
created-by: isim
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
name: http
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status: {}
oldObject: null
operation: CREATE
resource:
group: apps
resource: deployments
version: v1
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931
userInfo:
groups:
- system:masters
- system:authenticated
username: minikube-user
response:
allowed: true
patch: W3sib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvdGVtcGxhdGUvc3BlYy9jb250YWluZXJzLzAiLCJ2YWx1ZSI6eyJuYW1lIjoibGlua2VyZC1wcm94eSIsImltYWdlIjoiZ2NyLmlvL2xpbmtlcmQtaW8vcHJveHk6djE4LjguNCIsInBvcnRzIjpbeyJuYW1lIjoibGlua2VyZC1wcm94eSIsImNvbnRhaW5lclBvcnQiOjQxNDN9LHsibmFtZSI6ImxpbmtlcmQtbWV0cmljcyIsImNvbnRhaW5lclBvcnQiOjQxOTF9XSwiZW52IjpbeyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfTE9HIiwidmFsdWUiOiJ3YXJuLGxpbmtlcmQyX3Byb3h5PWluZm8ifSx7Im5hbWUiOiJMSU5LRVJEMl9QUk9YWV9CSU5EX1RJTUVPVVQiLCJ2YWx1ZSI6IjEwcyJ9LHsibmFtZSI6IkxJTktFUkQyX1BST1hZX0NPTlRST0xfVVJMIiwidmFsdWUiOiJ0Y3A6Ly9wcm94eS1hcGkubGlua2VyZC5zdmMuY2x1c3Rlci5sb2NhbDo4MDg2In0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfQ09OVFJPTF9MSVNURU5FUiIsInZhbHVlIjoidGNwOi8vMC4wLjAuMDo0MTkwIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfTUVUUklDU19MSVNURU5FUiIsInZhbHVlIjoidGNwOi8vMC4wLjAuMDo0MTkxIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfT1VUQk9VTkRfTElTVEVORVIiLCJ2YWx1ZSI6InRjcDovLzEyNy4wLjAuMTo0MTQwIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfSU5CT1VORF9MSVNURU5FUiIsInZhbHVlIjoidGNwOi8vMC4wLjAuMDo0MTQzIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfUE9EX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19LHsibmFtZSI6IkxJTktFUkQyX1BST1hZX1RMU19UUlVTVF9BTkNIT1JTIiwidmFsdWUiOiIvdmFyL2xpbmtlcmQtaW8vdHJ1c3QtYW5jaG9ycy90cnVzdC1hbmNob3JzLnBlbSJ9LHsibmFtZSI6IkxJTktFUkQyX1BST1hZX1RMU19DRVJUIiwidmFsdWUiOiIvdmFyL2xpbmtlcmQtaW8vaWRlbnRpdHkvY2VydGlmaWNhdGUuY3J0In0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfVExTX1BSSVZBVEVfS0VZIiwidmFsdWUiOiIvdmFyL2xpbmtlcmQtaW8vaWRlbnRpdHkvcHJpdmF0ZS1rZXkucDgifSx7Im5hbWUiOiJMSU5LRVJEMl9QUk9YWV9UTFNfUE9EX0lERU5USVRZIiwidmFsdWUiOiJuZ2lueC5kZXBsb3ltZW50Lmt1YmUtcHVibGljLmxpbmtlcmQtbWFuYWdlZC5saW5rZXJkLnN2Yy5jbHVzdGVyLmxvY2FsIn0seyJuYW1lIjoiTElOS0VSRDJfUFJPWFlfQ09OVFJPTExFUl9OQU1FU1BBQ0UiLCJ2YWx1ZSI6ImxpbmtlcmQifSx7Im5hbWUiOiJMSU5LRVJEMl9QUk9YWV9UTFNfQ09OVFJPTExFUl9JREVOVElUWSIsInZhbHVlIjoiY29udHJvbGxlci5kZXBsb3ltZW50LmxpbmtlcmQubGlua2VyZC1tYW5hZ2VkLmxpbmtlcmQuc3ZjLmNsdXN0ZXIubG9jYWwifV0sInJlc291cmNlcyI6e30sInZvbHVtZU1vdW50cyI6W3sibmFtZSI6ImxpbmtlcmQtdHJ1c3QtYW5jaG9ycyIsInJlYWRPbmx5Ijp0cnVlLCJtb3VudFBhdGgiOiIvdmFyL2xpbmtlcmQtaW8vdHJ1c3QtYW5jaG9ycyJ9LHsibmFtZSI6ImxpbmtlcmQtc2VjcmV0cyIsInJlYWRPbmx5Ijp0cnVlLCJtb3VudFBhdGgiOiIvdmFyL2xpbmtlcmQtaW8vaWRlbnRpdHkifV0sImxpdmVuZXNzUHJvYmUiOnsiaHR0cEdldCI6eyJwYXRoIjoiL21ldHJpY3MiLCJwb3J0Ijo0MTkxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6MTB9LCJyZWFkaW5lc3NQcm9iZSI6eyJodHRwR2V0Ijp7InBhdGgiOiIvbWV0cmljcyIsInBvcnQiOjQxOTF9LCJpbml0aWFsRGVsYXlTZWNvbmRzIjoxMH0sInRlcm1pbmF0aW9uTWVzc2FnZVBvbGljeSI6IkZhbGxiYWNrVG9Mb2dzT25FcnJvciIsImltYWdlUHVsbFBvbGljeSI6IklmTm90UHJlc2VudCIsInNlY3VyaXR5Q29udGV4dCI6eyJydW5Bc1VzZXIiOjIxMDJ9fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9zcGVjL2luaXRDb250YWluZXJzIiwidmFsdWUiOltdfSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL3RlbXBsYXRlL3NwZWMvaW5pdENvbnRhaW5lcnMvMCIsInZhbHVlIjp7Im5hbWUiOiJsaW5rZXJkLWluaXQiLCJpbWFnZSI6Imdjci5pby9saW5rZXJkLWlvL3Byb3h5LWluaXQ6djE4LjguNCIsImFyZ3MiOlsiLS1pbmNvbWluZy1wcm94eS1wb3J0IiwiNDE0MyIsIi0tb3V0Z29pbmctcHJveHktcG9ydCIsIjQxNDAiLCItLXByb3h5LXVpZCIsIjIxMDIiLCItLWluYm91bmQtcG9ydHMtdG8taWdub3JlIiwiNDE5MCw0MTkxIl0sInJlc291cmNlcyI6e30sInRlcm1pbmF0aW9uTWVzc2FnZVBvbGljeSI6IkZhbGxiYWNrVG9Mb2dzT25FcnJvciIsImltYWdlUHVsbFBvbGljeSI6IklmTm90UHJlc2VudCIsInNlY3VyaXR5Q29udGV4dCI6eyJjYXBhYmlsaXRpZXMiOnsiYWRkIjpbIk5FVF9BRE1JTiJdfSwicHJpdmlsZWdlZCI6ZmFsc2V9fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9zcGVjL3ZvbHVtZXMiLCJ2YWx1ZSI6W119LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvdGVtcGxhdGUvc3BlYy92b2x1bWVzLzAiLCJ2YWx1ZSI6eyJuYW1lIjoibGlua2VyZC10cnVzdC1hbmNob3JzIiwiY29uZmlnTWFwIjp7Im5hbWUiOiJsaW5rZXJkLWNhLWJ1bmRsZSIsIm9wdGlvbmFsIjp0cnVlfX19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvdGVtcGxhdGUvc3BlYy92b2x1bWVzLzAiLCJ2YWx1ZSI6eyJuYW1lIjoibGlua2VyZC1zZWNyZXRzIiwic2VjcmV0Ijp7InNlY3JldE5hbWUiOiJuZ2lueC1kZXBsb3ltZW50LXRscy1saW5rZXJkLWlvIiwib3B0aW9uYWwiOnRydWV9fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9tZXRhZGF0YS9sYWJlbHMiLCJ2YWx1ZSI6eyJsaW5rZXJkLmlvL2NvbnRyb2wtcGxhbmUtbnMiOiJsaW5rZXJkIiwibGlua2VyZC5pby9wcm94eS1kZXBsb3ltZW50IjoibmdpbngifX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy90ZW1wbGF0ZS9tZXRhZGF0YS9hbm5vdGF0aW9ucyIsInZhbHVlIjp7ImxpbmtlcmQuaW8vY3JlYXRlZC1ieSI6ImxpbmtlcmQvcHJveHktaW5qZWN0b3IgdjE4LjguNCIsImxpbmtlcmQuaW8vcHJveHktdmVyc2lvbiI6InYxOC44LjQifX1d
patchType: JSONPatch
uid: 3c3c45ff-bee9-11e8-9c41-b4d755961931

View File

@ -0,0 +1,60 @@
env:
- name: LINKERD2_PROXY_LOG
value: warn,linkerd2_proxy=info
- name: LINKERD2_PROXY_BIND_TIMEOUT
value: 10s
- name: LINKERD2_PROXY_CONTROL_URL
value: tcp://proxy-api.linkerd.svc.cluster.local:8086
- name: LINKERD2_PROXY_CONTROL_LISTENER
value: tcp://0.0.0.0:4190
- name: LINKERD2_PROXY_METRICS_LISTENER
value: tcp://0.0.0.0:4191
- name: LINKERD2_PROXY_OUTBOUND_LISTENER
value: tcp://127.0.0.1:4140
- name: LINKERD2_PROXY_INBOUND_LISTENER
value: tcp://0.0.0.0:4143
- name: LINKERD2_PROXY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LINKERD2_PROXY_TLS_TRUST_ANCHORS
value: /var/linkerd-io/trust-anchors/trust-anchors.pem
- name: LINKERD2_PROXY_TLS_CERT
value: /var/linkerd-io/identity/certificate.crt
- name: LINKERD2_PROXY_TLS_PRIVATE_KEY
value: /var/linkerd-io/identity/private-key.p8
- name: LINKERD2_PROXY_TLS_POD_IDENTITY
value: nginx.deployment.default.linkerd-managed.linkerd.svc.cluster.local
- name: LINKERD2_PROXY_CONTROLLER_NAMESPACE
value: linkerd
- name: LINKERD2_PROXY_TLS_CONTROLLER_IDENTITY
value: controller.deployment.linkerd.linkerd-managed.linkerd.svc.cluster.local
image: gcr.io/linkerd-io/proxy:v18.8.4
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
name: linkerd-proxy
ports:
- containerPort: 4143
name: linkerd-proxy
- containerPort: 4191
name: linkerd-metrics
readinessProbe:
httpGet:
path: /metrics
port: 4191
initialDelaySeconds: 10
resources: {}
securityContext:
runAsUser: 2102
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- mountPath: /var/linkerd-io/trust-anchors
name: linkerd-trust-anchors
readOnly: true
- mountPath: /var/linkerd-io/identity
name: linkerd-secrets
readOnly: true

View File

@ -0,0 +1,4 @@
name: linkerd-trust-anchors
configMap:
name: linkerd-ca-bundle
optional: true

View File

@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: kube-public

View File

@ -0,0 +1,7 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: linkerd
labels:
linkerd.io/auto-inject: enabled

View File

@ -0,0 +1,221 @@
package fake
import (
"encoding/base64"
"io/ioutil"
"path/filepath"
yaml "github.com/ghodss/yaml"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
)
const (
DefaultControllerNamespace = "linkerd"
DefaultNamespace = "default"
FileProxySpec = "fake/data/config-proxy.yaml"
FileProxyInitSpec = "fake/data/config-proxy-init.yaml"
FileTLSTrustAnchorVolumeSpec = "fake/data/config-linkerd-trust-anchors.yaml"
FileTLSIdentityVolumeSpec = "fake/data/config-linkerd-secrets.yaml"
)
// Factory is a factory that can convert in-file YAML content into Kubernetes
// API objects.
type Factory struct {
rootDir string
}
// NewFactory returns a new instance of Fixture.
func NewFactory() *Factory {
return &Factory{rootDir: filepath.Join("fake", "data")}
}
// HTTPRequestBody returns the content of the specified file as a slice of
// bytes. If the file doesn't exist in the 'fake/data' folder, an error will be
// returned.
func (f *Factory) HTTPRequestBody(filename string) ([]byte, error) {
return ioutil.ReadFile(filepath.Join(f.rootDir, filename))
}
// AdmissionReview returns the content of the specified file as an
// AdmissionReview type. An error will be returned if:
// i. the file doesn't exist in the 'fake/data' folder or,
// ii. the file content isn't a valid YAML structure that can be unmarshalled
// into AdmissionReview type
func (f *Factory) AdmissionReview(filename string) (*admissionv1beta1.AdmissionReview, error) {
b, err := ioutil.ReadFile(filepath.Join(f.rootDir, filename))
if err != nil {
return nil, err
}
var admissionReview admissionv1beta1.AdmissionReview
if err := yaml.Unmarshal(b, &admissionReview); err != nil {
return nil, err
}
return &admissionReview, nil
}
// Deployment returns the content of the specified file as a Deployment type. An
// error will be returned if:
// i. the file doesn't exist in the 'fake/data' folder or
// ii. the file content isn't a valid YAML structure that can be unmarshalled
// into Deployment type
func (f *Factory) Deployment(filename string) (*appsv1.Deployment, error) {
b, err := ioutil.ReadFile(filepath.Join(f.rootDir, filename))
if err != nil {
return nil, err
}
var deployment appsv1.Deployment
if err := yaml.Unmarshal(b, &deployment); err != nil {
return nil, err
}
return &deployment, nil
}
// Container returns the content of the specified file as a Container type. An
// error will be returned if:
// i. the file doesn't exist in the 'fake/data' folder or
// ii. the file content isn't a valid YAML structure that can be unmarshalled
// into Container type
func (f *Factory) Container(filename string) (*corev1.Container, error) {
b, err := ioutil.ReadFile(filepath.Join(f.rootDir, filename))
if err != nil {
return nil, err
}
var container corev1.Container
if err := yaml.Unmarshal(b, &container); err != nil {
return nil, err
}
return &container, nil
}
// ConfigMap returns the content of the specified file as a ConfigMap type. An
// error will be returned if:
// i. the file doesn't exist in the 'fake/data' folder or
// ii. the file content isn't a valid YAML structure that can be unmarshalled
// into ConfigMap type
func (f *Factory) ConfigMap(filename string) (*corev1.ConfigMap, error) {
b, err := ioutil.ReadFile(filepath.Join(f.rootDir, filename))
if err != nil {
return nil, err
}
var configMap corev1.ConfigMap
if err := yaml.Unmarshal(b, &configMap); err != nil {
return nil, err
}
return &configMap, nil
}
// Namespace returns the content of the specified file as a Namespace type. An
// error will be returned if:
// i. the file doesn't exist in the 'fake/data' folder or
// ii. the file content isn't a valid YAML structure that can be unmarshalled
// into Namespace type
func (f *Factory) Namespace(filename string) (*corev1.Namespace, error) {
b, err := ioutil.ReadFile(filepath.Join(f.rootDir, filename))
if err != nil {
return nil, err
}
var namespace corev1.Namespace
if err := yaml.Unmarshal(b, &namespace); err != nil {
return nil, err
}
return &namespace, nil
}
// Volume returns the content of the specified file as a Volume type. An error
// will be returned if:
// i. the file doesn't exist in the 'fake/data' folder or
// ii. the file content isn't a valid YAML structure that can be unmarshalled
// into Volume type
func (f *Factory) Volume(filename string) (*corev1.Volume, error) {
b, err := ioutil.ReadFile(filepath.Join(f.rootDir, filename))
if err != nil {
return nil, err
}
var volume corev1.Volume
if err := yaml.Unmarshal(b, &volume); err != nil {
return nil, err
}
return &volume, nil
}
// CATrustAnchors creates a fake CA trust anchors and returns the name of the
// temporary file. Caller is responsible for deleting the file once it's done.
func (f *Factory) CATrustAnchors() (string, error) {
file, err := ioutil.TempFile("", "linkerd-fake-trust-anchors.pem")
if err != nil {
return "", nil
}
trustAnchorsPEM := []byte(`-----BEGIN CERTIFICATE-----
MIIBTzCB9qADAgECAgEBMAoGCCqGSM49BAMCMCcxJTAjBgNVBAMTHENsdXN0ZXIt
bG9jYWwgTWFuYWdlZCBQb2QgQ0EwHhcNMTgwOTA0MTQyMjM3WhcNMTkwOTA1MTQy
MjM3WjAnMSUwIwYDVQQDExxDbHVzdGVyLWxvY2FsIE1hbmFnZWQgUG9kIENBMFkw
EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj0LP/rUU/htkvPasq/+OIytK8WPI2zWt
4XkFH6eIap/wgOWJ+UMsSWz15Sj0QgnVzazFQ0BjXSlFGJVTkIMoEaMTMBEwDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiAWLxJI8P/Pn/fTU9wMEY6D
qztZiU7GJkLZDF/Xr6Su6wIhAPVznxMv1uA4P8hFRDdb4TyZ+3xI64a5UwoBnk99
gvKX
-----END CERTIFICATE-----`)
if err := ioutil.WriteFile(file.Name(), trustAnchorsPEM, 0400); err != nil {
return "", err
}
return file.Name(), nil
}
// CertFile returns a dummy base64-encoded PEM certificate file path. Caller is
// responsible for deleting the certificate after use by calling os.Remove(cert).
// This certificate matches the key generated by the Key() method.
func (f *Factory) CertFile() (string, error) {
cert := "MIIBcDCCARWgAwIBAgIBHDAKBggqhkjOPQQDAjAnMSUwIwYDVQQDExxDbHVzdGVyLWxvY2FsIE1hbmFnZWQgUG9kIENBMB4XDTE4MDkxNDE2NDg1NFoXDTE5MDkxNTE2NDg1NFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDTSUF/5+Co7z4NbV5Ui7XbhiFixQccyTKOYHbk4sKyMqwE9UNRBB5ILh3nEQxhaSswd+Yxxs1M393nHb4xkZW+jWTBXMFUGA1UdEQEB/wRLMEmCR2NvbnRyb2xsZXIuZGVwbG95bWVudC5saW5rZXJkLmxpbmtlcmQtbWFuYWdlZC5saW5rZXJkLnN2Yy5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYCIQDU6UtUxLQJ/TmWqzVFspXvD0e78xe80koj0ib9wARxIQIhAPVyv+1GaT472qgDXb+HglDK7ZeacEjCh9rEenefJd2w"
decodedCert, err := base64.StdEncoding.DecodeString(cert)
if err != nil {
return "", nil
}
certFile, err := ioutil.TempFile("", "")
if err != nil {
return "", nil
}
if err := ioutil.WriteFile(certFile.Name(), decodedCert, 0); err != nil {
return "", nil
}
return certFile.Name(), nil
}
// KeyFile returns a dummy base64-encoded private key file path. Caller is
// responsible for deleting the certificate after use by calling os.Remove(cert).
// This private key matches the certificate generated by the CertFile() method.
func (f *Factory) PrivateKey() (string, error) {
key := "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxCVpEGPQML6jJUczzrbTWxzbT+/fMxDGyPejdR3KVihRANCAAQ00lBf+fgqO8+DW1eVIu124YhYsUHHMkyjmB25OLCsjKsBPVDUQQeSC4d5xEMYWkrMHfmMcbNTN/d5x2+MZGVv"
decodedKey, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return "", err
}
keyFile, err := ioutil.TempFile("", "")
if err != nil {
return "", err
}
if err := ioutil.WriteFile(keyFile.Name(), decodedKey, 0); err != nil {
return "", err
}
return keyFile.Name(), nil
}

View File

@ -0,0 +1,90 @@
package injector
import (
corev1 "k8s.io/api/core/v1"
)
const (
patchPathContainer = "/spec/template/spec/containers/0"
patchPathInitContainerRoot = "/spec/template/spec/initContainers"
patchPathInitContainer = "/spec/template/spec/initContainers/0"
patchPathVolumeRoot = "/spec/template/spec/volumes"
patchPathVolume = "/spec/template/spec/volumes/0"
patchPathPodLabel = "/spec/template/metadata/labels"
patchPathPodAnnotation = "/spec/template/metadata/annotations"
)
// Patch represents a RFC 6902 patch document.
type Patch struct {
patchOps []*patchOp
}
// NewPatch returns a new instance of PodPatch.
func NewPatch() *Patch {
return &Patch{
patchOps: []*patchOp{},
}
}
func (p *Patch) addContainer(container *corev1.Container) {
p.patchOps = append(p.patchOps, &patchOp{
Op: "add",
Path: patchPathContainer,
Value: container,
})
}
func (p *Patch) addInitContainerRoot() {
p.patchOps = append(p.patchOps, &patchOp{
Op: "add",
Path: patchPathInitContainerRoot,
Value: []*corev1.Container{},
})
}
func (p *Patch) addInitContainer(container *corev1.Container) {
p.patchOps = append(p.patchOps, &patchOp{
Op: "add",
Path: patchPathInitContainer,
Value: container,
})
}
func (p *Patch) addVolumeRoot() {
p.patchOps = append(p.patchOps, &patchOp{
Op: "add",
Path: patchPathVolumeRoot,
Value: []*corev1.Volume{},
})
}
func (p *Patch) addVolume(volume *corev1.Volume) {
p.patchOps = append(p.patchOps, &patchOp{
Op: "add",
Path: patchPathVolume,
Value: volume,
})
}
func (p *Patch) addPodLabel(label map[string]string) {
p.patchOps = append(p.patchOps, &patchOp{
Op: "add",
Path: patchPathPodLabel,
Value: label,
})
}
func (p *Patch) addPodAnnotation(annotation map[string]string) {
p.patchOps = append(p.patchOps, &patchOp{
Op: "add",
Path: patchPathPodAnnotation,
Value: annotation,
})
}
// patchOp represents a RFC 6902 patch operation.
type patchOp struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}

View File

@ -0,0 +1,73 @@
package injector
import (
"reflect"
"testing"
"github.com/linkerd/linkerd2/controller/proxy-injector/fake"
k8sPkg "github.com/linkerd/linkerd2/pkg/k8s"
"k8s.io/api/core/v1"
)
func TestPatch(t *testing.T) {
fixture := fake.NewFactory()
trustAnchors, err := fixture.Volume("inject-trust-anchors-volume-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
secrets, err := fixture.Volume("inject-linkerd-secrets-volume-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
sidecar, err := fixture.Container("inject-sidecar-container-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
init, err := fixture.Container("inject-init-container-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
var (
controllerNamespace = "linkerd"
createdBy = "linkerd/cli v18.8.4"
)
actual := NewPatch()
actual.addContainer(sidecar)
actual.addInitContainerRoot()
actual.addInitContainer(init)
actual.addVolumeRoot()
actual.addVolume(trustAnchors)
actual.addVolume(secrets)
actual.addPodLabel(map[string]string{
k8sPkg.ControllerNSLabel: controllerNamespace,
k8sPkg.ProxyAutoInjectLabel: k8sPkg.ProxyAutoInjectCompleted,
})
actual.addPodAnnotation(map[string]string{
k8sPkg.CreatedByAnnotation: createdBy,
})
expected := NewPatch()
expected.patchOps = []*patchOp{
&patchOp{Op: "add", Path: patchPathContainer, Value: sidecar},
&patchOp{Op: "add", Path: patchPathInitContainerRoot, Value: []*v1.Container{}},
&patchOp{Op: "add", Path: patchPathInitContainer, Value: init},
&patchOp{Op: "add", Path: patchPathVolumeRoot, Value: []*v1.Volume{}},
&patchOp{Op: "add", Path: patchPathVolume, Value: trustAnchors},
&patchOp{Op: "add", Path: patchPathVolume, Value: secrets},
&patchOp{Op: "add", Path: patchPathPodLabel, Value: map[string]string{
k8sPkg.ControllerNSLabel: controllerNamespace,
k8sPkg.ProxyAutoInjectLabel: k8sPkg.ProxyAutoInjectCompleted,
}},
&patchOp{Op: "add", Path: patchPathPodAnnotation, Value: map[string]string{k8sPkg.CreatedByAnnotation: createdBy}},
}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Content mismatch\nExpected: %s\nActual: %s", expected, actual)
}
}

View File

@ -0,0 +1,109 @@
package injector
import (
"context"
"crypto/tls"
"encoding/json"
"io/ioutil"
"net/http"
pem "github.com/linkerd/linkerd2/pkg/tls"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/kubernetes"
)
// WebhookServer is the webhook's HTTP server. It has an embedded webhook which
// mutate all the requests.
type WebhookServer struct {
*http.Server
*Webhook
}
// NewWebhookServer returns a new instance of the WebhookServer.
func NewWebhookServer(client kubernetes.Interface, resources *WebhookResources, addr, controllerNamespace, certFile, keyFile string) (*WebhookServer, error) {
c, err := tlsConfig(certFile, keyFile)
if err != nil {
return nil, err
}
server := &http.Server{
Addr: addr,
TLSConfig: c,
}
webhook, err := NewWebhook(client, resources, controllerNamespace)
if err != nil {
return nil, err
}
ws := &WebhookServer{server, webhook}
ws.Handler = http.HandlerFunc(ws.serve)
return ws, nil
}
func (w *WebhookServer) serve(res http.ResponseWriter, req *http.Request) {
var (
data []byte
err error
)
if req.Body != nil {
data, err = ioutil.ReadAll(req.Body)
if err != nil {
http.Error(res, err.Error(), http.StatusInternalServerError)
return
}
}
if len(data) == 0 {
return
}
response := w.Mutate(data)
responseJSON, err := json.Marshal(response)
if err != nil {
http.Error(res, err.Error(), http.StatusInternalServerError)
return
}
if _, err := res.Write(responseJSON); err != nil {
http.Error(res, err.Error(), http.StatusInternalServerError)
return
}
}
// Shutdown initiates a graceful shutdown of the underlying HTTP server.
func (w *WebhookServer) Shutdown() error {
return w.Server.Shutdown(context.Background())
}
func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, err
}
keyBytes, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}
certPEM, err := pem.PEMEncodeCert(certBytes)
if err != nil {
return nil, err
}
log.Debugf("PEM-encoded certificate: %s\n", certPEM)
keyPEM, err := pem.PEMEncodeKey(keyBytes, pem.KeyTypeECDSA)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
}, nil
}

View File

@ -0,0 +1,111 @@
package injector
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"reflect"
"testing"
"github.com/linkerd/linkerd2/controller/proxy-injector/fake"
log "github.com/sirupsen/logrus"
)
var (
testServer *WebhookServer
testWebhookResources *WebhookResources
)
func init() {
// create a webhook which uses its fake client to seed the sidecar configmap
fakeClient, err := fake.NewClient("")
if err != nil {
panic(err)
}
testWebhookResources = &WebhookResources{
FileProxySpec: fake.FileProxySpec,
FileProxyInitSpec: fake.FileProxyInitSpec,
FileTLSTrustAnchorVolumeSpec: fake.FileTLSTrustAnchorVolumeSpec,
FileTLSIdentityVolumeSpec: fake.FileTLSIdentityVolumeSpec,
}
webhook, err = NewWebhook(fakeClient, testWebhookResources, fake.DefaultControllerNamespace)
if err != nil {
panic(err)
}
log.SetOutput(ioutil.Discard)
factory = fake.NewFactory()
factory = fake.NewFactory()
testServer = &WebhookServer{nil, webhook}
}
func TestServe(t *testing.T) {
t.Run("with empty http request body", func(t *testing.T) {
in := bytes.NewReader(nil)
request := httptest.NewRequest(http.MethodGet, "/", in)
recorder := httptest.NewRecorder()
testServer.serve(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("HTTP response status mismatch. Expected: %d. Actual: %d", http.StatusOK, recorder.Code)
}
if reflect.DeepEqual(recorder.Body.Bytes(), []byte("")) {
t.Errorf("Content mismatch. Expected HTTP response body to be empty %v", recorder.Body.Bytes())
}
})
}
func TestShutdown(t *testing.T) {
server := &http.Server{Addr: ":0"}
testServer := WebhookServer{server, nil}
go func() {
if err := testServer.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
t.Errorf("Expected server to be gracefully shutdown with error: %q", http.ErrServerClosed)
}
}
}()
if err := testServer.Shutdown(); err != nil {
t.Fatal("Unexpected error: ", err)
}
}
func TestNewWebhookServer(t *testing.T) {
certFile, err := factory.CertFile()
if err != nil {
t.Fatal("Unexpected error: ", err)
}
defer os.Remove(certFile)
keyFile, err := factory.PrivateKey()
if err != nil {
t.Fatal("Unexpected error: ", err)
}
defer os.Remove(keyFile)
var (
addr = ":7070"
kubeconfig = ""
)
fakeClient, err := fake.NewClient(kubeconfig)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
server, err := NewWebhookServer(fakeClient, testWebhookResources, addr, fake.DefaultControllerNamespace, certFile, keyFile)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if server.Addr != fmt.Sprintf("%s", addr) {
t.Errorf("Expected server address to be :%q", addr)
}
}

View File

@ -0,0 +1,26 @@
package tmpl
var MutatingWebhookConfigurationSpec = `
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: {{ .WebhookConfigName }}
webhooks:
- name: {{ .WebhookServiceName }}
clientConfig:
service:
name: proxy-injector
namespace: {{ .ControllerNamespace }}
path: "/"
caBundle: {{ .CABundle }}
rules:
- operations: [ "CREATE" ]
apiGroups: ["apps", "extensions"]
apiVersions: ["v1", "v1beta1", "v1beta2"]
resources: ["deployments"]
namespaceSelector:
matchExpressions:
- key: {{.ProxyAutoInjectLabel}}
operator: NotIn
values:
- "disabled"`

View File

@ -0,0 +1,274 @@
package injector
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
yaml "github.com/ghodss/yaml"
"github.com/linkerd/linkerd2/pkg/healthcheck"
k8sPkg "github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/kubernetes"
)
const (
defaultNamespace = "default"
envVarKeyProxyTLSPodIdentity = "LINKERD2_PROXY_TLS_POD_IDENTITY"
envVarKeyProxyTLSControllerIdentity = "LINKERD2_PROXY_TLS_CONTROLLER_IDENTITY"
)
// Webhook is a Kubernetes mutating admission webhook that mutates pods admission
// requests by injecting sidecar container spec into the pod spec during pod
// creation.
type Webhook struct {
deserializer runtime.Decoder
controllerNamespace string
resources *WebhookResources
}
// NewWebhook returns a new instance of Webhook.
func NewWebhook(client kubernetes.Interface, resources *WebhookResources, controllerNamespace string) (*Webhook, error) {
var (
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
)
return &Webhook{
deserializer: codecs.UniversalDeserializer(),
controllerNamespace: controllerNamespace,
resources: resources,
}, nil
}
// Mutate changes the given pod spec by injecting the proxy sidecar container
// into the spec. The admission review object returns contains the original
// request and the response with the mutated pod spec.
func (w *Webhook) Mutate(data []byte) *admissionv1beta1.AdmissionReview {
admissionReview, err := w.decode(data)
if err != nil {
log.Error("failed to decode data. Reason: ", err)
admissionReview.Response = &admissionv1beta1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: false,
Result: &metav1.Status{
Message: err.Error(),
},
}
return admissionReview
}
log.Infof("received admission review request %s", admissionReview.Request.UID)
log.Debugf("admission request: %+v", admissionReview.Request)
admissionResponse, err := w.inject(admissionReview.Request)
if err != nil {
log.Error("failed to inject sidecar. Reason: ", err)
admissionReview.Response = &admissionv1beta1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: false,
Result: &metav1.Status{
Message: err.Error(),
},
}
return admissionReview
}
admissionReview.Response = admissionResponse
if len(admissionResponse.Patch) > 0 {
log.Infof("patch generated: %s", admissionResponse.Patch)
}
log.Info("done")
return admissionReview
}
func (w *Webhook) decode(data []byte) (*admissionv1beta1.AdmissionReview, error) {
var admissionReview admissionv1beta1.AdmissionReview
err := yaml.Unmarshal(data, &admissionReview)
return &admissionReview, err
}
func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admissionv1beta1.AdmissionResponse, error) {
var deployment appsv1.Deployment
if err := yaml.Unmarshal(request.Object.Raw, &deployment); err != nil {
return nil, err
}
log.Infof("working on %s/%s %s..", request.Kind.Version, strings.ToLower(request.Kind.Kind), deployment.ObjectMeta.Name)
ns := request.Namespace
if ns == "" {
ns = defaultNamespace
}
log.Infof("resource namespace: %s", ns)
if w.ignore(&deployment) {
log.Infof("ignoring deployment %s", deployment.ObjectMeta.Name)
return &admissionv1beta1.AdmissionResponse{
UID: request.UID,
Allowed: true,
}, nil
}
identity := &k8sPkg.TLSIdentity{
Name: deployment.ObjectMeta.Name,
Kind: strings.ToLower(request.Kind.Kind),
Namespace: ns,
ControllerNamespace: w.controllerNamespace,
}
proxy, proxyInit, err := w.containersSpec(identity)
if err != nil {
return nil, err
}
log.Infof("proxy image: %s", proxy.Image)
log.Infof("proxy-init image: %s", proxyInit.Image)
log.Debugf("proxy container: %+v", proxy)
log.Debugf("init container: %+v", proxyInit)
caBundle, tlsSecrets, err := w.volumesSpec(identity)
if err != nil {
return nil, err
}
log.Debugf("ca bundle volume: %+v", caBundle)
log.Debugf("tls secrets volume: %+v", tlsSecrets)
patch := NewPatch()
patch.addContainer(proxy)
if len(deployment.Spec.Template.Spec.InitContainers) == 0 {
patch.addInitContainerRoot()
}
patch.addInitContainer(proxyInit)
if len(deployment.Spec.Template.Spec.Volumes) == 0 {
patch.addVolumeRoot()
}
patch.addVolume(caBundle)
patch.addVolume(tlsSecrets)
patch.addPodLabel(map[string]string{
k8sPkg.ControllerNSLabel: w.controllerNamespace,
k8sPkg.ProxyDeploymentLabel: deployment.ObjectMeta.Name,
})
var (
image = strings.Split(proxy.Image, ":")
imageTag = ""
)
if len(image) < 2 {
imageTag = "latest"
} else {
imageTag = image[1]
}
patch.addPodAnnotation(map[string]string{
k8sPkg.CreatedByAnnotation: fmt.Sprintf("linkerd/proxy-injector %s", imageTag),
k8sPkg.ProxyVersionAnnotation: imageTag,
})
patchJSON, err := json.Marshal(patch.patchOps)
if err != nil {
return nil, err
}
patchType := admissionv1beta1.PatchTypeJSONPatch
admissionResponse := &admissionv1beta1.AdmissionResponse{
UID: request.UID,
Allowed: true,
Patch: patchJSON,
PatchType: &patchType,
}
return admissionResponse, nil
}
func (w *Webhook) ignore(deployment *appsv1.Deployment) bool {
labels := deployment.Spec.Template.ObjectMeta.GetLabels()
status, defined := labels[k8sPkg.ProxyAutoInjectLabel]
if defined {
switch status {
case k8sPkg.ProxyAutoInjectDisabled, k8sPkg.ProxyAutoInjectCompleted:
return true
}
}
return healthcheck.HasExistingSidecars(&deployment.Spec.Template.Spec)
}
func (w *Webhook) containersSpec(identity *k8sPkg.TLSIdentity) (*corev1.Container, *corev1.Container, error) {
proxySpec, err := ioutil.ReadFile(w.resources.FileProxySpec)
if err != nil {
return nil, nil, err
}
var proxy corev1.Container
if err := yaml.Unmarshal(proxySpec, &proxy); err != nil {
return nil, nil, err
}
for index, env := range proxy.Env {
if env.Name == envVarKeyProxyTLSPodIdentity {
proxy.Env[index].Value = identity.ToDNSName()
} else if env.Name == envVarKeyProxyTLSControllerIdentity {
proxy.Env[index].Value = identity.ToControllerIdentity().ToDNSName()
}
}
proxyInitSpec, err := ioutil.ReadFile(w.resources.FileProxyInitSpec)
if err != nil {
return nil, nil, err
}
var proxyInit corev1.Container
if err := yaml.Unmarshal(proxyInitSpec, &proxyInit); err != nil {
return nil, nil, err
}
return &proxy, &proxyInit, nil
}
func (w *Webhook) volumesSpec(identity *k8sPkg.TLSIdentity) (*corev1.Volume, *corev1.Volume, error) {
trustAnchorVolumeSpec, err := ioutil.ReadFile(w.resources.FileTLSTrustAnchorVolumeSpec)
if err != nil {
return nil, nil, err
}
var trustAnchors corev1.Volume
if err := yaml.Unmarshal(trustAnchorVolumeSpec, &trustAnchors); err != nil {
return nil, nil, err
}
tlsVolumeSpec, err := ioutil.ReadFile(w.resources.FileTLSIdentityVolumeSpec)
if err != nil {
return nil, nil, err
}
var linkerdSecrets corev1.Volume
if err := yaml.Unmarshal(tlsVolumeSpec, &linkerdSecrets); err != nil {
return nil, nil, err
}
linkerdSecrets.VolumeSource.Secret.SecretName = identity.ToSecretName()
return &trustAnchors, &linkerdSecrets, nil
}
// WebhookResources contain paths to all the needed file resources.
type WebhookResources struct {
// FileProxySpec is the path to the proxy spec.
FileProxySpec string
// FileProxyInitSpec is the path to the proxy-init spec.
FileProxyInitSpec string
// FileTLSTrustAnchorVolumeSpec is the path to the trust anchor volume spec.
FileTLSTrustAnchorVolumeSpec string
// FileTLSIdentityVolumeSpec is the path to the TLS identity volume spec.
FileTLSIdentityVolumeSpec string
}

View File

@ -0,0 +1,113 @@
package injector
import (
"bytes"
"encoding/base64"
"io/ioutil"
"text/template"
yaml "github.com/ghodss/yaml"
"github.com/linkerd/linkerd2/controller/proxy-injector/tmpl"
k8sPkg "github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
arv1beta1 "k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// WebhookConfig creates the MutatingWebhookConfiguration of the webhook.
type WebhookConfig struct {
controllerNamespace string
webhookServiceName string
trustAnchor []byte
configTemplate *template.Template
k8sAPI kubernetes.Interface
}
// NewWebhookConfig returns a new instance of initiator.
func NewWebhookConfig(client kubernetes.Interface, controllerNamespace, webhookServiceName, trustAnchorFile string) (*WebhookConfig, error) {
trustAnchor, err := ioutil.ReadFile(trustAnchorFile)
if err != nil {
return nil, err
}
t := template.New(k8sPkg.ProxyInjectorWebhookConfig)
return &WebhookConfig{
controllerNamespace: controllerNamespace,
webhookServiceName: webhookServiceName,
trustAnchor: trustAnchor,
configTemplate: template.Must(t.Parse(tmpl.MutatingWebhookConfigurationSpec)),
k8sAPI: client,
}, nil
}
// CreateOrUpdate sends the request to either create or update the
// MutatingWebhookConfiguration resource. During an update, only the CA bundle
// is changed.
func (w *WebhookConfig) CreateOrUpdate() (*arv1beta1.MutatingWebhookConfiguration, error) {
mwc, exist, err := w.exist()
if err != nil {
return nil, err
}
if !exist {
return w.create()
}
return w.update(mwc)
}
// exist returns true if the mutating webhook configuration exists. Otherwise,
// it returns false.
func (w *WebhookConfig) exist() (*arv1beta1.MutatingWebhookConfiguration, bool, error) {
mwc, err := w.k8sAPI.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Get(k8sPkg.ProxyInjectorWebhookConfig, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return nil, false, nil
}
return nil, false, err
}
return mwc, true, nil
}
func (w *WebhookConfig) create() (*arv1beta1.MutatingWebhookConfiguration, error) {
var (
buf = &bytes.Buffer{}
spec = struct {
WebhookConfigName string
WebhookServiceName string
ControllerNamespace string
CABundle string
ProxyAutoInjectLabel string
}{
WebhookConfigName: k8sPkg.ProxyInjectorWebhookConfig,
WebhookServiceName: w.webhookServiceName,
ControllerNamespace: w.controllerNamespace,
CABundle: base64.StdEncoding.EncodeToString(w.trustAnchor),
ProxyAutoInjectLabel: k8sPkg.ProxyAutoInjectLabel,
}
)
if err := w.configTemplate.Execute(buf, spec); err != nil {
return nil, err
}
var config arv1beta1.MutatingWebhookConfiguration
if err := yaml.Unmarshal(buf.Bytes(), &config); err != nil {
log.Infof("failed to unmarshal mutating webhook configuration: %s\n%s\n", err, buf.String())
return nil, err
}
return w.k8sAPI.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&config)
}
func (w *WebhookConfig) update(mwc *arv1beta1.MutatingWebhookConfiguration) (*arv1beta1.MutatingWebhookConfiguration, error) {
for i := 0; i < len(mwc.Webhooks); i++ {
mwc.Webhooks[i].ClientConfig.CABundle = w.trustAnchor
}
return w.k8sAPI.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Update(mwc)
}

View File

@ -0,0 +1,63 @@
package injector
import (
"io/ioutil"
"log"
"os"
"testing"
"github.com/linkerd/linkerd2/controller/proxy-injector/fake"
)
func TestCreateOrUpdate(t *testing.T) {
var (
factory = fake.NewFactory()
namespace = fake.DefaultControllerNamespace
webhookServiceName = "test.linkerd.io"
)
log.SetOutput(ioutil.Discard)
client, err := fake.NewClient("")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
trustAnchorsPath, err := factory.CATrustAnchors()
if err != nil {
t.Fatal("Unexpected error: ", err)
}
defer os.Remove(trustAnchorsPath)
webhookConfig, err := NewWebhookConfig(client, namespace, webhookServiceName, trustAnchorsPath)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
// expect mutating webhook configuration to not exist
_, exist, err := webhookConfig.exist()
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if exist {
t.Error("Unexpected mutating webhook configuration. Expect resources to not exist")
}
// create the mutating webhook configuration
if _, err := webhookConfig.CreateOrUpdate(); err != nil {
t.Fatal("Unexpected error: ", err)
}
// expect mutating webhook configuration to exist
_, exist, err = webhookConfig.exist()
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if !exist {
t.Error("Expected mutating webhook configuration to exist")
}
// update the mutating webhook configuration using the same trust anchors
if _, err := webhookConfig.CreateOrUpdate(); err != nil {
t.Fatal("Unexpected error: ", err)
}
}

View File

@ -0,0 +1,181 @@
package injector
import (
"fmt"
"io/ioutil"
"reflect"
"testing"
"github.com/linkerd/linkerd2/controller/proxy-injector/fake"
"github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
)
var (
webhook *Webhook
factory *fake.Factory
)
func init() {
// create a webhook which uses its fake client to seed the sidecar configmap
fakeClient, err := fake.NewClient("")
if err != nil {
panic(err)
}
webhook, err = NewWebhook(fakeClient, testWebhookResources, fake.DefaultControllerNamespace)
if err != nil {
panic(err)
}
log.SetOutput(ioutil.Discard)
}
func TestMutate(t *testing.T) {
var testCases = []struct {
title string
requestFile string
responseFile string
}{
{title: "no labels", requestFile: "inject-no-labels-request.json", responseFile: "inject-no-labels-response.yaml"},
{title: "inject enabled", requestFile: "inject-enabled-request.json", responseFile: "inject-enabled-response.yaml"},
{title: "inject disabled", requestFile: "inject-disabled-request.json", responseFile: "inject-disabled-response.yaml"},
{title: "inject completed", requestFile: "inject-completed-request.json", responseFile: "inject-completed-response.yaml"},
}
for _, testCase := range testCases {
t.Run(fmt.Sprintf("%s", testCase.title), func(t *testing.T) {
data, err := factory.HTTPRequestBody(testCase.requestFile)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
expected, err := factory.AdmissionReview(testCase.responseFile)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
actual := webhook.Mutate(data)
assertEqualAdmissionReview(t, expected, actual)
})
}
}
func TestIgnore(t *testing.T) {
t.Run("by checking labels", func(t *testing.T) {
var testCases = []struct {
filename string
expected bool
}{
{filename: "deployment-inject-status-empty.yaml", expected: false},
{filename: "deployment-inject-status-enabled.yaml", expected: false},
{filename: "deployment-inject-status-disabled.yaml", expected: true},
{filename: "deployment-inject-status-completed.yaml", expected: true},
}
for id, testCase := range testCases {
t.Run(fmt.Sprintf("%d", id), func(t *testing.T) {
deployment, err := factory.Deployment(testCase.filename)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if actual := webhook.ignore(deployment); actual != testCase.expected {
t.Errorf("Boolean mismatch. Expected: %t. Actual: %t", testCase.expected, actual)
}
})
}
})
t.Run("by checking container spec", func(t *testing.T) {
deployment, err := factory.Deployment("deployment-with-injected-proxy.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if !webhook.ignore(deployment) {
t.Errorf("Expected deployment with injected proxy to be ignored")
}
})
}
func TestContainersSpec(t *testing.T) {
expectedSidecar, err := factory.Container("inject-sidecar-container-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
expectedInit, err := factory.Container("inject-init-container-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
identity := &k8s.TLSIdentity{
Name: "nginx",
Kind: "deployment",
Namespace: fake.DefaultNamespace,
ControllerNamespace: fake.DefaultControllerNamespace,
}
actualSidecar, actualInit, err := webhook.containersSpec(identity)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if !reflect.DeepEqual(expectedSidecar, actualSidecar) {
t.Errorf("Content mismatch\nExpected: %+v\nActual: %+v", expectedSidecar, actualSidecar)
}
if !reflect.DeepEqual(expectedInit, actualInit) {
t.Errorf("Content mismatch\nExpected: %+v\nActual: %+v", expectedInit, actualInit)
}
}
func TestVolumesSpec(t *testing.T) {
expectedTrustAnchors, err := factory.Volume("inject-trust-anchors-volume-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
expectedLinkerdSecrets, err := factory.Volume("inject-linkerd-secrets-volume-spec.yaml")
if err != nil {
t.Fatal("Unexpected error: ", err)
}
identity := &k8s.TLSIdentity{
Name: "nginx",
Kind: "deployment",
Namespace: fake.DefaultNamespace,
ControllerNamespace: fake.DefaultControllerNamespace,
}
actualTrustAnchors, actualLinkerdSecrets, err := webhook.volumesSpec(identity)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if !reflect.DeepEqual(expectedTrustAnchors, actualTrustAnchors) {
t.Errorf("Content mismatch\nExpected: %+v\nActual: %+v", expectedTrustAnchors, actualTrustAnchors)
}
if !reflect.DeepEqual(expectedLinkerdSecrets, actualLinkerdSecrets) {
t.Errorf("Content mismatch\nExpected: %+v\nActual: %+v", expectedLinkerdSecrets, actualLinkerdSecrets)
}
}
func assertEqualAdmissionReview(t *testing.T, expected, actual *admissionv1beta1.AdmissionReview) {
if !reflect.DeepEqual(expected.Request, actual.Request) {
if !reflect.DeepEqual(expected.Request.Object, actual.Request.Object) {
t.Errorf("Request object mismatch\nExpected: %s\nActual: %s", expected.Request.Object, actual.Request.Object)
} else {
t.Errorf("Request mismatch\nExpected: %+v\nActual: %+v", expected.Request, actual.Request)
}
}
if !reflect.DeepEqual(expected.Response, actual.Response) {
if actual.Response.Result != nil {
t.Errorf("Actual response message: %s", actual.Response.Result.Message)
}
t.Errorf("Response patch mismatch\nExpected: %s\nActual: %s", expected.Response.Patch, actual.Response.Patch)
}
}

View File

@ -0,0 +1,37 @@
package healthcheck
import (
"strings"
corev1 "k8s.io/api/core/v1"
)
// HasExistingSidecars returns true if the pod spec already has the proxy init
// and sidecar cntainers injected. Otherwise, it returns false.
func HasExistingSidecars(podSpec *corev1.PodSpec) bool {
for _, container := range podSpec.Containers {
if strings.HasPrefix(container.Image, "gcr.io/linkerd-io/proxy:") ||
strings.HasPrefix(container.Image, "gcr.io/istio-release/proxyv2:") ||
strings.HasPrefix(container.Image, "gcr.io/heptio-images/contour:") ||
strings.HasPrefix(container.Image, "docker.io/envoyproxy/envoy-alpine:") ||
container.Name == "linkerd-proxy" ||
container.Name == "istio-proxy" ||
container.Name == "contour" ||
container.Name == "envoy" {
return true
}
}
for _, ic := range podSpec.InitContainers {
if strings.HasPrefix(ic.Image, "gcr.io/linkerd-io/proxy-init:") ||
strings.HasPrefix(ic.Image, "gcr.io/istio-release/proxy_init:") ||
strings.HasPrefix(ic.Image, "gcr.io/heptio-images/contour:") ||
ic.Name == "linkerd-init" ||
ic.Name == "istio-init" ||
ic.Name == "envoy-initconfig" {
return true
}
}
return false
}

View File

@ -62,6 +62,22 @@ const (
// (e.g. v0.1.3).
ProxyVersionAnnotation = "linkerd.io/proxy-version"
// ProxyAutoInjectLabel indicates if sidecar auto-inject should be performed
// on the pod. Supported values are "enabled", "disabled" or "completed".
ProxyAutoInjectLabel = "linkerd.io/auto-inject"
// ProxyAutoInjectEnabled is assigned to the ProxyAutoInjectLabel label to
// indicate that the sidecar auto-inject is enabled for a particular resource.
ProxyAutoInjectEnabled = "enabled"
// ProxyAutoInjectDisabled is assigned to the ProxyAutoInjectLabel label to
// indicate that the sidecar auto-inject is disabled for a particular resource.
ProxyAutoInjectDisabled = "disabled"
// ProxyAutoInjectDisabled is assigned to the ProxyAutoInjectLabel label to
// indicate that the sidecar auto-inject is completed for a particular resource.
ProxyAutoInjectCompleted = "completed"
/*
* Component Names
*/
@ -72,6 +88,35 @@ const (
// ProxyContainerName is the name assigned to the injected proxy container.
ProxyContainerName = "linkerd-proxy"
// ProxyInjectorTLSSecret is the name assigned to the secret containing the
// TLS cert and key used by the proxy-injector webhook.
ProxyInjectorTLSSecret = "proxy-injector-service-tls-linkerd-io"
// ProxyInjectorWebhookConfig is the name of the mutating webhook
// configuration resource of the proxy-injector webhook.
ProxyInjectorWebhookConfig = "linkerd-proxy-injector-webhook-config"
// ProxyInjectorSidecarConfig is the name of the config map resource that
// contains the specs of the proxy init container and sidecar container to be
// injected into a pod.
ProxyInjectorSidecarConfig = "proxy-injector-sidecar-config"
// ProxySpecFileName is the name (key) within the proxy-injector ConfigMap
// that contains the proxy container spec.
ProxySpecFileName = "proxy.yaml"
// ProxyInjectorInitContainerSpecFileName is the name (key) within the
// proxy-injector ConfigMap that contains the proxy-init container spec.
ProxyInitSpecFileName = "proxy-init.yaml"
// TLSTrustAnchorVolumeSpecFileName is the name (key) within the
// proxy-injector ConfigMap that contains the trust anchors volume spec.
TLSTrustAnchorVolumeSpecFileName = "linkerd-trust-anchors.yaml"
// TLSIdentityVolumeSpecFileName is the name (key) within the
// proxy-injector ConfigMap that contains the TLS identity secrets volume spec.
TLSIdentityVolumeSpecFileName = "linkerd-secrets.yaml"
// TLSTrustAnchorConfigMapName is the name of the ConfigMap that holds the
// trust anchors (trusted root certificates).
TLSTrustAnchorConfigMapName = "linkerd-ca-bundle"
@ -82,6 +127,43 @@ const (
TLSCertFileName = "certificate.crt"
TLSPrivateKeyFileName = "private-key.p8"
/*
* Mount paths
*/
// MountPathBase is the base directory of the mount path
MountPathBase = "/var/linkerd-io"
)
var (
// MountPathTLSTrustAnchor is the path at which the trust anchor file is
// mounted
MountPathTLSTrustAnchor = MountPathBase + "/trust-anchors/" + TLSTrustAnchorFileName
// MountPathTLSIdentityCert is the path at which the TLS identity cert file is
// mounted
MountPathTLSIdentityCert = MountPathBase + "/identity/" + TLSCertFileName
// MountPathTLSIdentityKey is the path at which the TLS identity key file is
// mounted
MountPathTLSIdentityKey = MountPathBase + "/identity/" + TLSPrivateKeyFileName
// MountPathConfigProxySpec is the path at which the proxy container spec is
// mounted to the proxy-injector
MountPathConfigProxySpec = MountPathBase + "/config/" + ProxySpecFileName
// MountPathConfigProxyInitSpec is the path at which the proxy-init container
// spec is mounted to the proxy-injector
MountPathConfigProxyInitSpec = MountPathBase + "/config/" + ProxyInitSpecFileName
// MountTLSPathTrustAnchorVolumeSpec is the path at which the trust anchor
// volume spec is mounted to the proxy-injector
MountPathTLSTrustAnchorVolumeSpec = MountPathBase + "/config/" + TLSTrustAnchorVolumeSpecFileName
// MountPathTLSIdentityVolumeSpec is the path at which the TLS identity
// secret volume spec is mounted to the proxy-injector
MountPathTLSIdentityVolumeSpec = MountPathBase + "/config/" + TLSIdentityVolumeSpecFileName
)
// CreatedByAnnotationValue returns the value associated with
@ -133,6 +215,9 @@ type TLSIdentity struct {
}
func (i TLSIdentity) ToDNSName() string {
if i.Kind == Service {
return fmt.Sprintf("%s.%s.svc", i.Name, i.Namespace)
}
return fmt.Sprintf("%s.%s.%s.linkerd-managed.%s.svc.cluster.local", i.Name,
i.Kind, i.Namespace, i.ControllerNamespace)
}

36
pkg/tls/tls.go Normal file
View File

@ -0,0 +1,36 @@
package tls
import (
"bytes"
"encoding/pem"
"fmt"
)
const (
KeyTypeRSA = "rsa"
KeyTypeECDSA = "ecdsa"
)
// PEMEncodeCertToMemory returns the PEM encoding of cert.
func PEMEncodeCert(cert []byte) ([]byte, error) {
buf := &bytes.Buffer{}
err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
return buf.Bytes(), err
}
// PEMEncodeKey returns the PEM encoding of key.
func PEMEncodeKey(key []byte, keyType string) ([]byte, error) {
pemBlock := &pem.Block{Bytes: key}
switch keyType {
case KeyTypeRSA:
pemBlock.Type = "RSA PRIVATE KEY"
case KeyTypeECDSA:
pemBlock.Type = "EC PRIVATE KEY"
default:
return nil, fmt.Errorf("Unknown key type")
}
buf := &bytes.Buffer{}
err := pem.Encode(buf, pemBlock)
return buf.Bytes(), err
}

View File

@ -1,5 +1,5 @@
## compile proxy-init utility
FROM gcr.io/linkerd-io/go-deps:64a32a2a as golang
FROM gcr.io/linkerd-io/go-deps:0c590d0e as golang
WORKDIR /go/src/github.com/linkerd/linkerd2
COPY ./proxy-init ./proxy-init
RUN CGO_ENABLED=0 GOOS=linux go install -v ./proxy-init/

View File

@ -24,7 +24,7 @@ ENV NODE_ENV production
RUN $ROOT/bin/web build
## compile go server
FROM gcr.io/linkerd-io/go-deps:64a32a2a as golang
FROM gcr.io/linkerd-io/go-deps:0c590d0e as golang
WORKDIR /go/src/github.com/linkerd/linkerd2
COPY web web
COPY controller controller