Skip to content

Commit

Permalink
Add function in translator to fetch secrets for Ingress
Browse files Browse the repository at this point in the history
  • Loading branch information
rramkumar1 committed May 27, 2020
1 parent 357db47 commit fb59808
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 39 deletions.
72 changes: 72 additions & 0 deletions hack/run-local-e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#/bin/bash

# Setup the environment for running the e2e tests from your
# desktop.

set -e

parseCluster() {
# These are all globals.
net=$1
subnet=$2
zone=$3
selfLink=$4
net=$(echo ${net} | sed 's+.*networks/\([-a-z]*\).*$+\1+')
subnet=$(echo ${subnet} | sed 's+.*subnetworks/\([-a-z]*\)$+\1+')
project=$(echo ${selfLink} | sed 's+.*/projects/\([-a-z]*\)/.*+\1+')
}

parseInstance() {
local name=$1
local zone=$2
# Globals.
nodeTag=$(gcloud compute instances describe ${name} --zone ${zone} --format='value(tags.items[0])')
}

clusterName="$1"
clusterLocation="$2"

if [ -z "${clusterName}" ]; then
echo "Usage: $0 CLUSTER_NAME [LOCATION]"
echo
echo "LOCATION is optional if there is only one cluster with CLUSTER_NAME"
exit 1
fi

fmt='value(networkConfig.network,networkConfig.subnetwork,zone,selfLink,name)'
if [ -z "$clusterLocation" ]; then
clusters=$(gcloud container clusters list --format="${fmt}" --filter="name=${clusterName}")
else
clusters=$(gcloud container clusters list --format="${fmt}" --filter="name=${clusterName} location=${clusterLocation}")
fi
if [ $(echo "${cluster}" | wc -l) -gt 1 ]; then
echo "ERROR: more than one cluster matches '${clusterName}'"
fi
parseCluster ${clusters}
if [ -z "${clusters}" ]; then
echo "ERROR: No cluster '${clusterName}' found"
exit 1
fi

instance=$(gcloud compute instances list --format='value(name,zone)' | grep ${clusterName} | tail -n 1)
parseInstance ${instance}
if [ -z "${instance}" ]; then
echo "ERROR: No nodes matching '${clusterName}' found"
exit 1
fi

gceConf="/tmp/gce.conf"
echo "Writing ${gceConf}"
echo "----"
cat <<EOF | tee ${gceConf}
[global]
token-url = nil
project-id = ${project}
network-name = ${net}
subnetwork-name = ${subnet}
node-instance-prefix = ${clusterName}
node-tags = ${nodeTag}
local-zone = ${zone}
EOF

echo "Run glbc with hack/run-glbc.sh"
46 changes: 46 additions & 0 deletions hack/run-local-glbc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#/bin/bash

# Run glbc. First run `run-local.sh` to set things up.
#
# Files touched: /tmp/kubectl-proxy.log /tmp/glbc.log

GOOGLE_APPLICATION_CREDENTIALS="${HOME}/.config/gcloud/application_default_credentials.json"

if [ ! -r ${GOOGLE_APPLICATION_CREDENTIALS} ]; then
echo "You must login your application default credentials"
echo "$ gcloud auth application-default login"
exit 1
fi

GCECONF=${GCECONF:-/tmp/gce.conf}
GLBC=${GLBC:-./glbc}
PORT=${PORT:-7127}
V=${V:-3}

echo "GCECONF=${GCECONF} GLBC=${GLBC} PORT=${PORT} V=${V}"

if [ ! -x "${GLBC}" ]; then
echo "ERROR: No ${GLBC} executable found" >&2
exit 1
fi

echo "$(date) start" >> /tmp/kubectl-proxy.log
kubectl proxy --port="${PORT}" \
>> /tmp/kubectl-proxy.log &

PROXY_PID=$!
cleanup() {
echo "Killing proxy (pid=${PROXY_PID})"
kill ${PROXY_PID}
}
trap cleanup EXIT

kubectl apply -f docs/deploy/resources/default-http-backend.yaml

sleep 2 # Wait for proxy to start up
${GLBC} \
--apiserver-host=http://localhost:${PORT} \
--running-in-cluster=false \
--logtostderr --v=${V} \
--config-file-path=${GCECONF} \
| tee -a /tmp/glbc.log#!/bin/bash
55 changes: 16 additions & 39 deletions pkg/tls/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,70 +17,48 @@ limitations under the License.
package tls

import (
"context"
"fmt"

"k8s.io/klog"

api_v1 "k8s.io/api/core/v1"
"k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/ingress-gce/pkg/loadbalancers"
"k8s.io/ingress-gce/pkg/translator"
)

// TlsLoader is the interface for loading the relevant TLSCerts for a given ingress.
type TlsLoader interface {
// Load loads the relevant TLSCerts based on ing.Spec.TLS
Load(ing *v1beta1.Ingress) ([]*loadbalancers.TLSCerts, error)
// Validate validates the given TLSCerts and returns an error if they are invalid.
Validate(certs *loadbalancers.TLSCerts) error
}

// TODO: Add better cert validation.
type noOPValidator struct{}

func (n *noOPValidator) Validate(certs *loadbalancers.TLSCerts) error {
return nil
}

// TLSCertsFromSecretsLoader loads TLS certs from kubernetes secrets.
type TLSCertsFromSecretsLoader struct {
noOPValidator
Client kubernetes.Interface
}

// Ensure that TLSCertsFromSecretsLoader implements TlsLoader interface.
var _ TlsLoader = &TLSCertsFromSecretsLoader{}

func (t *TLSCertsFromSecretsLoader) Load(ing *v1beta1.Ingress) ([]*loadbalancers.TLSCerts, error) {
if len(ing.Spec.TLS) == 0 {
return nil, nil
env, err := translator.NewEnv(ing, t.Client)
if err != nil {
return nil, fmt.Errorf("error initializing translator env: %v", err)
}

var certs []*loadbalancers.TLSCerts

for _, tlsSecret := range ing.Spec.TLS {
if tlsSecret.SecretName == "" {
continue
}
// TODO: Replace this for a secret watcher.
klog.V(3).Infof("Retrieving secret for ing %v with name %v", ing.Name, tlsSecret.SecretName)
secret, err := t.Client.CoreV1().Secrets(ing.Namespace).Get(context.TODO(), tlsSecret.SecretName, meta_v1.GetOptions{})
if err != nil {
return nil, err
}
cert, ok := secret.Data[api_v1.TLSCertKey]
if !ok {
return nil, fmt.Errorf("secret %v has no 'tls.crt'", tlsSecret.SecretName)
}
key, ok := secret.Data[api_v1.TLSPrivateKeyKey]
if !ok {
return nil, fmt.Errorf("secret %v has no 'tls.key'", tlsSecret.SecretName)
}
newCert := &loadbalancers.TLSCerts{Key: string(key), Cert: string(cert), Name: tlsSecret.SecretName,
CertHash: loadbalancers.GetCertHash(string(cert))}
if err := t.Validate(newCert); err != nil {
return nil, err
secrets, err := translator.Secrets(env)
if err != nil {
return nil, fmt.Errorf("error getting secrets for Ingress: %v", err)
}
for _, secret := range secrets {
cert := string(secret.Data[api_v1.TLSCertKey])
newCert := &loadbalancers.TLSCerts{
Key: string(secret.Data[api_v1.TLSPrivateKeyKey]),
Cert: cert,
Name: secret.Name,
CertHash: loadbalancers.GetCertHash(cert),
}
certs = append(certs, newCert)
}
Expand All @@ -91,7 +69,6 @@ func (t *TLSCertsFromSecretsLoader) Load(ing *v1beta1.Ingress) ([]*loadbalancers

// FakeTLSSecretLoader fakes out TLS loading.
type FakeTLSSecretLoader struct {
noOPValidator
FakeCerts map[string]*loadbalancers.TLSCerts
}

Expand Down
60 changes: 60 additions & 0 deletions pkg/translator/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,77 @@ limitations under the License.
package translator

import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"

"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
api_v1 "k8s.io/api/core/v1"
"k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"k8s.io/ingress-gce/pkg/composite"
"k8s.io/ingress-gce/pkg/utils"
"k8s.io/ingress-gce/pkg/utils/namer"
)

// Env contains all k8s-style configuration needed to perform the translation.
type Env struct {
// Ing is the Ingress we are translating.
Ing *v1beta1.Ingress
// SecretsMap contains a mapping from Secret name to the actual resource.
// It is assumed that the map contains resources from a single namespace.
// This is the same namespace as the Ingress namespace.
SecretsMap map[string]*api_v1.Secret
}

// NewEnv returns an Env for the given Ingress.
func NewEnv(ing *v1beta1.Ingress, client kubernetes.Interface) (*Env, error) {
ret := &Env{Ing: ing, SecretsMap: make(map[string]*api_v1.Secret)}
secrets, err := client.CoreV1().Secrets(ing.Namespace).List(context.TODO(), meta_v1.ListOptions{})
if err != nil {
return nil, err
}
for _, s := range secrets.Items {
s := s
ret.SecretsMap[s.Name] = &s
}
return ret, nil
}

// Translator implements the mapping between an MCI and its corresponding GCE resources.
type Translator struct{}

// NewTranslator returns a new Translator.
func NewTranslator() *Translator {
return &Translator{}
}

// Secrets returns the Secrets from the environment which are specified in the Ingress.
func Secrets(env *Env) ([]*api_v1.Secret, error) {
var ret []*api_v1.Secret
spec := env.Ing.Spec
for _, tlsSpec := range spec.TLS {
secret, ok := env.SecretsMap[tlsSpec.SecretName]
if !ok {
return nil, fmt.Errorf("secret %q does not exist", tlsSpec.SecretName)
}
// Fail-fast if the user's secret does not have the proper fields specified.
if secret.Data[api_v1.TLSCertKey] == nil {
return nil, fmt.Errorf("secret %q does not specify cert as string data", tlsSpec.SecretName)
}
if secret.Data[api_v1.TLSPrivateKeyKey] == nil {
return nil, fmt.Errorf("secret %q does not specify private key as string data", tlsSpec.SecretName)
}
ret = append(ret, secret)
}

return ret, nil
}

// The gce api uses the name of a path rule to match a host rule.
const hostRulePrefix = "host"

Expand Down
Loading

0 comments on commit fb59808

Please sign in to comment.