Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#313] Add auth for EDI #705

Merged
merged 22 commits into from
Dec 29, 2018
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ localDomain: "cluster.local"
addLocalDomain: true
s3BucketName: "{{ legion_data_s3_bucket }}"

{% if dex.enabled == true %}
auth:
enabled: true
annotations:
nginx.ingress.kubernetes.io/auth-signin: https://auth.{{ cluster_name }}/oauth2/start?rd=https://$host$request_uri$is_args$args
nginx.ingress.kubernetes.io/auth-url: http://oauth2-proxy.kube-system.svc.cluster.local:4180/oauth2/auth
{% endif %}

package:
version: "{{ legion_version }}"
repository: "{{ pypi_repo }}"
Expand All @@ -21,7 +29,7 @@ jenkins:
enabled: true
persistence:
storageClass: {% if persistent_jenkins_volume %}"jenkins-volume"{% else %}""{% endif %}

size: {{ jenkins_volume_size | default('20') }}Gi

dags_volume_pvc: "{{ airflow_dags_pvc }}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ namespace: default

log_level: {{ log_level }}

{% if dex.enabled == true %}
auth:
enabled: true
annotations:
nginx.ingress.kubernetes.io/auth-signin: https://auth.{{ cluster_name }}/oauth2/start?rd=https://$host$request_uri$is_args$args
nginx.ingress.kubernetes.io/auth-url: http://oauth2-proxy.kube-system.svc.cluster.local:4180/oauth2/auth
{% endif %}

package:
version: "{{ legion_version }}"
repository: "{{ pypi_repo }}"
Expand Down Expand Up @@ -33,12 +41,6 @@ grafana:
ingress:
tls:
enabled: {% if use_https == "yes" %}true{% else %}false{% endif %}
{% if dex.enabled == true %}

annotations:
nginx.ingress.kubernetes.io/auth-signin: https://auth.{{ cluster_name }}/oauth2/start?rd=https://$host$request_uri$is_args$args
nginx.ingress.kubernetes.io/auth-url: http://oauth2-proxy.kube-system.svc.cluster.local:4180/oauth2/auth
{% endif %}

edge:
enabled: true
Expand Down
13 changes: 12 additions & 1 deletion deploy/helms/legion-core/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,15 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
{{- define "fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}

{{/*
Create dex Ingress auth annotations
*/}}
{{- define "dex-ingress-annotations" }}
{{- if .Values.auth.enabled -}}
{{- range $key, $value := .Values.auth.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ spec:
- name: LEGION_BASE_IMAGE_REPOSITORY
value: "{{ .Values.package.baseImage.repository }}"
# EDI credentials
- name: EDI_USER
value: ""
- name: EDI_PASSOWRD
value: ""
- name: EDI_TOKEN
value: ""
# External resource (saving and loading model files)
- name: EXTERNAL_RESOURCE_PROTOCOL
value: "http"
Expand Down
1 change: 1 addition & 0 deletions deploy/helms/legion-core/templates/metrics/grafana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ metadata:
{{- range $key, $value := .Values.grafana.ingress.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- template "dex-ingress-annotations" . }}
labels:
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
Expand Down
7 changes: 4 additions & 3 deletions deploy/helms/legion-core/templates/nexus/nexus-ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ metadata:
release: {{ .Release.Name | quote }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
annotations:
{{- range $key, $value := .Values.nexus.ingress.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- range $key, $value := .Values.nexus.ingress.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- template "dex-ingress-annotations" . }}
spec:
rules:
- host: "{{ .Values.nexus.ingress.domain.partial }}.{{ .Values.rootDomain }}"
Expand Down
6 changes: 5 additions & 1 deletion deploy/helms/legion-core/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ enclaveDeploymentPrefix: "legion-"
domainDelimiter: "."
s3BucketName: ""

auth:
enabled: false
annotations: {}

secrets:
nexus:
admin: "jonny"
Expand All @@ -16,7 +20,7 @@ jenkins:
pullPolicy: "Always"

rbac: true

ingress:
enabled: true
annotations: {}
Expand Down
12 changes: 12 additions & 0 deletions deploy/helms/legion/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{/* vim: set filetype=mustache: */}}

{{/*
Create dex Ingress auth annotations
*/}}
{{- define "dex-ingress-annotations" }}
{{- if .Values.auth.enabled -}}
{{- range $key, $value := .Values.auth.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
1 change: 1 addition & 0 deletions deploy/helms/legion/templates/edi/edi-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ metadata:
{{- range $key, $value := .Values.edi.ingress.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- template "dex-ingress-annotations" . }}
labels:
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
Expand Down
1 change: 1 addition & 0 deletions deploy/helms/legion/templates/metrics/grafana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ metadata:
{{- range $key, $value := .Values.grafana.ingress.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- template "dex-ingress-annotations" . }}
labels:
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
Expand Down
16 changes: 10 additions & 6 deletions deploy/helms/legion/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ clusterName: ""

log_level: info

auth:
enabled: false
annotations: {}

feedback:
replicas: 1
ingress:
annotations: {}
domain:
partial: feedback
tls:
enabled: true
autoSecretNameDeduction: true
annotations: {}
domain:
partial: feedback
tls:
enabled: true
autoSecretNameDeduction: true

image:
repository: "legion/k8s-fluentd"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ def pod(Map podParams=null, Closure body) {
envToPass = [
"LEGION_PACKAGE_VERSION", "LEGION_PACKAGE_REPOSITORY", "LEGION_BASE_IMAGE_TAG",
"LEGION_BASE_IMAGE_REPOSITORY",
"EDI_USER", "EDI_PASSOWRD", "EDI_TOKEN",
"EXTERNAL_RESOURCE_PROTOCOL", "EXTERNAL_RESOURCE_HOST", "EXTERNAL_RESOURCE_USER", "EXTERNAL_RESOURCE_PASSWORD",
"MODEL_IMAGES_REGISTRY", "MODEL_IMAGES_REGISTRY_HOST", "DOCKER_REGISTRY_USER", "DOCKER_REGISTRY_PASSWORD",
"GRAPHITE_HOST", "STATSD_HOST", "STATSD_PORT",
Expand Down
8 changes: 7 additions & 1 deletion legion/bin/legionctl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import argparse
import logging

import legion.docker_bootup
from legion.edi.security import login, add_edi_arguments
from legion.serving.pyserve import serve_model
from legion.external.edi import add_edi_arguments, add_arguments_for_wait_operation
from legion.external.edi import add_arguments_for_wait_operation
from legion.edi.deploy import \
build_model, \
deploy_kubernetes, undeploy_kubernetes, scale_kubernetes, inspect_kubernetes, \
Expand Down Expand Up @@ -54,6 +55,11 @@ if __name__ == '__main__':
action='store_true')
subparsers = parser.add_subparsers()

# --------- LOGIN SECTION -----------
login_parser = subparsers.add_parser('login', description='Save edi credentials to the config')
add_edi_arguments(login_parser, required=True)
login_parser.set_defaults(func=login)

# --------- LOCAL DOCKER SECTION -----------
build_parser = subparsers.add_parser('build', description='build model into new docker image (should be run '
'in the docker container)')
Expand Down
4 changes: 2 additions & 2 deletions legion/docs/source/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ dex:
- email: tests-user@legion.com
password: ~
```
* Export the following environment variables:
* Go to the `tests\robot` directory. Export the following environment variables:
```bash
export PROFILE="legion-test.epm.kharlamov.biz"
export LEGION_VERSION="0.10.0-20181213084714.1329.6ba06cc"
export PATH_TO_COOKIES="credentials.secret"
export CLUSTER_NAME="legion-test.epm.kharlamov.biz"
export CLUSTER_NAME="${PROFILE}"
export CREDENTIAL_SECRETS=".secrets.yaml"
export ROBOT_OPTIONS="-v PATH_TO_PROFILES_DIR:../../deploy/profiles"
```
Expand Down
6 changes: 3 additions & 3 deletions legion/legion/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@
GRAFANA_USER = 'GRAFANA_USER', 'admin'
GRAFANA_PASSWORD = 'GRAFANA_PASSWORD', 'admin'

EDI_URL = 'EDI_URL', 'http://localhost:5001/'
EDI_USER = 'EDI_USER', ''
EDI_PASSWORD = 'EDI_PASSWORD', ''
EDI_URL = 'EDI_URL', ''
EDI_TOKEN = 'EDI_TOKEN', ''

LEGION_CONFIG = 'LEGION_CONFIG', ''

LOCAL_DEFAULT_RESOURCE_PREFIX = 'LOCAL_DEFAULT_RESOURCE_PREFIX', ''
EXTERNAL_RESOURCE_USE_BY_DEFAULT = 'EXTERNAL_RESOURCE_USE_BY_DEFAULT', 'true'
EXTERNAL_RESOURCE_PROTOCOL = 'EXTERNAL_RESOURCE_PROTOCOL', 'https'
Expand Down
136 changes: 136 additions & 0 deletions legion/legion/edi/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#
# Copyright 2018 EPAM Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Security logic for legion
"""

import configparser
import logging
import os
from pathlib import Path

from legion import config
from legion.external import edi

_DEFAULT_CONFIG_PATH = Path.home().joinpath('.legion/config')
LOG = logging.getLogger(__name__)


def add_edi_arguments(parser, required=False):
"""
Add EDI arguments parser

:param parser: add arguments to it
:type parser: argparse.ArgumentParser
:param required: (Optional) mark edi arguments as required if it equals True
:type required: bool
:return: None
"""
parser.add_argument('--edi',
type=str, help='EDI server host', required=required)
parser.add_argument('--token',
type=str, help='EDI server token', required=required)


def _get_config_location():
"""
Return the config path.
LEGION_CONFIG can override path value

:return: Path -- config path
"""
config_path_from_env = os.getenv(*config.LEGION_CONFIG)

return Path(config_path_from_env) if config_path_from_env else _DEFAULT_CONFIG_PATH


def _check_credentials(args):
"""
Make a request to the server to make sure that credentials are correct

:param args: command arguments with .namespace
:type args: argparse.Namespace
:return None
"""
# url and token must presents in args
edi_clint = edi.build_client(args)

edi_clint.info()


def _save_credentials(edi, token):
"""
Save credentials to the config file.
If file or dir doesn't exist then it will be created.
While we store only security parameters, func can override existed parameters

:param edi: edi url
:type edi: str
:param token: dex token
:type token: str
:return None
"""
config_path = _get_config_location()

config_path.parent.mkdir(mode=0o775, parents=True, exist_ok=True)
config_path.touch(mode=0o600, exist_ok=True)

config = configparser.ConfigParser()
config['security'] = {
'host': edi,
'token': token
}

with config_path.open("w") as config_file:
config.write(config_file)

LOG.debug(f'Save config {config} to the file {config_path}')


def login(args):
"""
Check that credentials is correct and save to the config

:param args: command arguments
:type args: argparse.Namespace
:return: None
"""
_check_credentials(args)
_save_credentials(args.edi, args.token)

LOG.info('Success! Credentials were saved.')


def get_security_params_from_config():
"""
Return dict with edi url and token from config.
If an exception occurs during parsing of config or config file doesn't exist then
return empty dict

:return: dict[str, str] -- config
"""
config_path = _get_config_location()

if config_path.exists():
try:
config = configparser.ConfigParser()
config.read(_get_config_location())

return dict(config['security'])
except Exception as e:
LOG.debug(f'Exception during parsing of legion config {e}')

return {}
Loading