Commit f93dd24d authored by Adam Lewenberg's avatar Adam Lewenberg
Browse files

add session affinity to backend crd

parent c45788dd
......@@ -177,6 +177,13 @@ IdP. To get around this, we create a custom health-check using a
BackendConfig CRD. This BackendConfig tells the Ingress to make an HTTP GET to
the path `/Shibboleth.sso/Metadata` which is _not_ SAML-protected.
In the BackendConfig CRD we set session affinity to `GENERATED_COOKIE` to
ensure that a client do an initial SAML authentication will get the same
backend Pod for the entire SAML authentication process. The TTL for this
cookie should be the same length as the allowed maximum SAML
authentication session time which, as of this writing, is 5 minutes. See
also [Google's "Configuring Ingress features" page][7] and [Shibboleth's
SP Clustering page][8].
### Secrets
......@@ -235,3 +242,7 @@ The SAML entity ID for this Service Provider will be the URL
[5]: https://helm.sh/docs/chart_template_guide/subcharts_and_globals/
[6]: https://github.com/GoogleCloudPlatform/gke-managed-certs
[7]: https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-features#session_affinity
[8]: https://wiki.shibboleth.net/confluence/display/SP3/Clustering
......@@ -4,6 +4,8 @@ import pprint
import yaml
import os
# Diagram of the apache-shib Kubernetes project:
#
# Deployment
# DeploymentSpec
# PodTemplateSpec
......@@ -44,35 +46,83 @@ from kubernetes.client.models.v1_pod import *
## Make a class for our Apache Shib Pod
class Project():
ATTRIBUTE_MAP = {
'app_name': 'apache-shib',
'app_namespace': None,
'image': None,
'image_tag': None,
'image_tag': 'latest',
'image_pull_policy': 'Always',
'apache_port': '80',
'server_name': None,
'replicas': 1,
}
def __init__(self, values = None, read_via = 'env_var'):
name = values['name']
def __init__(self, values = None, debug = True):
self.debug = debug
# Algorithm to set attr_values.
# 1. Set defaults from ATTRIBUTE_MAP (but use the lowercase version)
# 2. If values is not None then any defined keys in value will override.
# 3. If load_via is passed use that method to override.
# 1. Load defaults from ATTRIBUTE_MAP.
attr_values = Project.ATTRIBUTE_MAP.copy()
# 2. Overwrite with passed in values
if (values is not None):
self.progress("[__init__] loading from the 'values' parameter")
for key, value in values:
attr_values[key] = value
else:
self.progress("[__init__] not loading from the 'values' parameter (was not provided)")
for key, value in Project.ATTRIBUTE_MAP.items():
setattr(self, key, value)
# 3. Load from the environment variables.
self.progress('[__init__] loading values from the environment')
self.load_values_from_environment(attr_values)
self.container = ApacheShibContainer(values=values)
self.pod_spec = ApacheShibPodSpec(containers=[self.container])
# Ensure that all the keys from ATTRIBUTE_MAP are defined.
for key in Project.ATTRIBUTE_MAP:
if (attr_values[key] is None):
raise Exception(f"the required value '{key}' is not defined")
# We are now ready to set the attributes
for key in Project.ATTRIBUTE_MAP:
self.progress(f"[__init__] setting attribute '{key}' to value '{attr_values[key]}'")
setattr(self, key, attr_values[key])
name = attr_values['app_name']
self.progress('setting Container...')
self.container = ApacheShibContainer(values=attr_values)
if (self.debug):
print(pprint.pformat(self.container))
self.progress('setting PodSpec...')
self.pod_spec = ApacheShibPodSpec(containers=[self.container])
if (self.debug):
print(pprint.pformat(self.pod_spec))
self.progress('setting PodTemplateSpec...')
self.pod_template_spec = ApacheShibPodTemplateSpec(spec=self.pod_spec)
self.deployment_spec = ApacheShibDeploymentSpec(pod_template_spec=self.pod_template_spec)
self.deployment = ApacheShibDeployment(name=name, spec=self.deployment_spec)
self.progress('setting DeploymentSpec...')
self.deployment_spec = ApacheShibDeploymentSpec(pod_template_spec=self.pod_template_spec)
self.progress('setting Deployment...')
self.deployment = ApacheShibDeployment(name=name, spec=self.deployment_spec)
def to_str(self):
"""Returns the string representation of the model"""
return pprint.pformat(self.deployment.to_dict())
def progress(self, msg):
if (self.debug):
print(f"progress: {msg}")
def __repr__(self):
"""For `print` and `pprint`"""
return self.to_str()
......@@ -85,9 +135,14 @@ class Project():
data = yaml.safe_load(open(yaml_file))
return data
def load_values_from_environment(self):
if (os.environ['app_namespace']):
settatr(self, 'app_namespace', os.environ['app_namespace'])
def load_values_from_environment(self, attr_values):
for key in Project.ATTRIBUTE_MAP:
if (key.upper() in os.environ):
env_val = os.environ[key.upper()]
self.progress(f"setting attribute '{key}' from environment variable '{key.upper()}' to value '{env_val}'")
attr_values[key] = env_val
else:
self.progress(f"not setting attribute '{key}' from environment (env variable '{key.upper()}' not defined)")
class ApacheShibDeployment(V1Deployment):
def __init__(self, name, spec):
......@@ -121,18 +176,19 @@ class ApacheShibPodSpec(V1PodSpec):
volumes=[vol_saml_crt, vol_saml_key],
)
class ApacheShibContainer(V1Container):
def __init__(self, values):
name = values['name']
name = values['app_name']
super(ApacheShibContainer, self).__init__(name=name)
# Docker image
self.image = values['image']
self.image_pull_policy = values['imagePullPolicy']
self.image_pull_policy = values['image_pull_policy']
# Port
port = V1ContainerPort(name='http', container_port='80', protocol='TCP')
port = V1ContainerPort(name='http',
container_port=values['apache_port'],
protocol='TCP')
self.ports = [port]
# Readiness and liveness probes
......@@ -145,9 +201,12 @@ class ApacheShibContainer(V1Container):
self.liveness_probe = readiness_probe
# Environment variables
server_name = values['server_name']
shib_entity_id = f"https://{server_name}"
env_vars = [
V1EnvVar(name='SHIB_ENTITY_ID', value=values['SHIB_ENTITY_ID']),
V1EnvVar(name='SERVER_NAME', value=values['SERVER_NAME']),
V1EnvVar(name='SERVER_NAME', value=server_name),
V1EnvVar(name='SHIB_ENTITY_ID', value=shib_entity_id),
]
self.env = env_vars
......@@ -166,30 +225,31 @@ class ApacheShibContainer(V1Container):
#################################################################
values = {
'name': 'httpd',
'image': 'nginx',
'imagePullPolicy': 'always',
'SERVER_NAME': 'example.com',
'SHIB_ENTITY_ID': 'https://example.com',
}
yaml_file = './test.yaml'
values = Project.load_values_from_yaml(yaml_file)
config.load_kube_config()
container = V1Container(name='nginx')
#container = ApacheShibContainer(values=values)
#pod_spec = ApacheShibPodSpec(containers=[container])
#pod_template_spec = ApacheShibPodTemplateSpec(spec=pod_spec)
#deployment_spec = ApacheShibDeploymentSpec(pod_template_spec=pod_template_spec)
#deployment = ApacheShibDeployment(name='zzzzzzz', spec=deployment_spec)
project = Project(values)
project.load_values_from_environment()
#values = {
# 'name': 'httpd',
# 'image': 'nginx',
# 'imagePullPolicy': 'always',
# 'SERVER_NAME': 'example.com',
# 'SHIB_ENTITY_ID': 'https://example.com',
#}
#
#yaml_file = './test.yaml'
#values = Project.load_values_from_yaml(yaml_file)
#
#config.load_kube_config()
#container = V1Container(name='nginx')
#
##container = ApacheShibContainer(values=values)
##pod_spec = ApacheShibPodSpec(containers=[container])
##pod_template_spec = ApacheShibPodTemplateSpec(spec=pod_spec)
##deployment_spec = ApacheShibDeploymentSpec(pod_template_spec=pod_template_spec)
##deployment = ApacheShibDeployment(name='zzzzzzz', spec=deployment_spec)
os.environ["APP_NAMESPACE"] = 'test-dev'
os.environ["IMAGE"] = 'busybox'
os.environ["SERVER_NAME"] = 'iedo.stanford.edu'
project = Project()
#print(project)
#print(project.to_yaml())
......@@ -210,7 +270,7 @@ api_instance = client.CoreV1Api()
def create_ns(namespace):
ns = client.V1Namespace(metadata=client.V1ObjectMeta(name=namespace))
resp = api_instance.create_namespace(body=ns)
print resp
print(resp)
def create_pod(namespace):
pod_name = 'nginx'
......
......@@ -10,3 +10,14 @@ spec:
port: 80
type: HTTP
requestPath: /Shibboleth.sso/Metadata
sessionAffinity:
# We set session affinity to GENERATED_COOKIE to ensure that a client
# do an initial SAML authentication will get the same backend Pod for
# the entire SAML authentication process. The TTL for this cookie
# should be the same length as the allowed maximum SAML authentication
# session time which, as of this writing, is 5 minutes.
# See also:
# https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-features#session_affinity
# https://wiki.shibboleth.net/confluence/display/SP3/Clustering
affinityType: "GENERATED_COOKIE"
affinityCookieTtlSec: 300
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment