Skip to content

Commit 2ee8c64

Browse files
author
Colin Toal
committed
Added Pykerberize support script and raise exceptions from errors in ktadd method
1 parent bedffe9 commit 2ee8c64

File tree

9 files changed

+438
-51
lines changed

9 files changed

+438
-51
lines changed

.circleci/config.yml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
version: 2
2+
jobs:
3+
test:
4+
docker:
5+
- image: circleci/python:3.6.1
6+
steps:
7+
- checkout
8+
- restore_cache:
9+
keys:
10+
- v1-dependencies-{{ checksum "requirements.txt" }}
11+
- v1-dependencies-
12+
- run:
13+
name: Establish secure tunnel
14+
command: |
15+
SSH_PARAMS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=10"
16+
ssh $SSH_PARAMS -f -N -L 4443:python.internal.integrateai.net:443 ansible@ansible.integrateai.net
17+
- run:
18+
name: install dependencies
19+
command: |
20+
python3 -m venv venv
21+
. venv/bin/activate
22+
pip install -r requirements.txt --extra-index-url https://localhost:4443 --trusted-host localhost:4443
23+
- save_cache:
24+
paths:
25+
- ./venv
26+
key: v1-dependencies-{{ checksum "requirements.txt" }}
27+
- run:
28+
name: run tests
29+
command: |
30+
. venv/bin/activate
31+
pytest
32+
33+
build-ami:
34+
docker:
35+
- image: circleci/python:3.6.1
36+
steps:
37+
- checkout
38+
- restore_cache:
39+
keys:
40+
- v1-dependencies-{{ checksum "requirements.txt" }}
41+
- v1-dependencies-
42+
- run:
43+
name: install dependencies
44+
command: |
45+
python3 -m venv venv
46+
. venv/bin/activate
47+
pip install awscli
48+
- run:
49+
name: Deploy and Build AMI
50+
# Connect to Ansible and run deployment
51+
command: |
52+
SSH_PARAMS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=10"
53+
ssh $SSH_PARAMS ansible@ansible.integrateai.net git -C ~ansible/deployment pull origin master
54+
ssh $SSH_PARAMS ansible@ansible.integrateai.net << EOF
55+
cd deployment
56+
python3 deploy.py --direct model-registry $CIRCLE_TAG
57+
EOF
58+
59+
deploy-to-s3:
60+
docker:
61+
- image: circleci/python:3.6.1
62+
steps:
63+
- checkout
64+
- restore_cache:
65+
keys:
66+
- v1-dependencies-{{ checksum "requirements.txt" }}
67+
- v1-dependencies-
68+
- run:
69+
name: install dependencies
70+
command: |
71+
python3 -m venv venv
72+
. venv/bin/activate
73+
pip install awscli
74+
- run:
75+
name: Deploy to S3
76+
command: |
77+
. venv/bin/activate
78+
python3 setup.py sdist
79+
aws s3 cp ~/project/dist/iai-model-registry-*.tar.gz s3://deployment.integrate.ai/python/iai-model-registry/
80+
81+
workflows:
82+
version: 2
83+
test-and-deploy:
84+
jobs:
85+
- test:
86+
filters:
87+
tags:
88+
only:
89+
- /^beta-.*$/
90+
- /^release-.*$/
91+
- build-ami:
92+
requires:
93+
- test
94+
filters:
95+
tags:
96+
only:
97+
- /^beta-.*$/
98+
- /^release-.*$/
99+
branches:
100+
ignore: /.*/
101+
- deploy-to-s3:
102+
requires:
103+
- test
104+
filters:
105+
branches:
106+
only: master

.editorconfig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
end_of_line = lf
9+
charset = utf-8
10+
11+
[*.py]
12+
max_line_length = 120
13+
14+
# Use 2 spaces for the HTML files
15+
[*.html]
16+
indent_size = 2
17+
18+
# The JSON files contain newlines inconsistently
19+
[*.{json,yml}]
20+
indent_size = 2
21+
insert_final_newline = ignore
22+
23+
# Makefiles always use tabs for indentation
24+
[Makefile]
25+
indent_style = tab

pykerberize/__init__.py

Whitespace-only changes.

pykerberize/pykerberize.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
## (c) 2018 integrate.ai
2+
## Helper module for use in EMR bootstrap actions and in
3+
## Corporate Realm machines that require keytabs
4+
##
5+
## Assumes SSM parameters in region like:
6+
##
7+
## <customer_identifier>-realm-name
8+
## <customer_indentifier>-realm-principal
9+
## <customer_identifier>-realm-principal-creds
10+
##
11+
## see github.com/deployment/terraform/emr/<customer_identifier>/ssm.tf
12+
##
13+
## If no customer identifier is passed, assumes:
14+
##
15+
## corp-iai-realm-name
16+
## corp-iai-realm-principal
17+
## corp-iai-realm-principal-creds
18+
##
19+
## Machine EC2 role must have access to decryption key
20+
## for these SSM Parameters in order to execute this
21+
## script successful. These keys are assumed, not checked
22+
## for.
23+
##
24+
## see github.com/deployment/terraform/network/auth_services.tf
25+
##
26+
## Only one public method:
27+
## create_host_principal_keytab( customer, keytabfilename )
28+
29+
import logging
30+
import boto3
31+
import kadmin
32+
import socket
33+
34+
35+
## Creates or gets the Host Principal and then makes a new
36+
## keytab file using the keytabfilename parameter
37+
## throws an exception if the keytabfilename exists and
38+
## contains an existing keytab
39+
def create_host_principal_keytab( customer, keytabfilename ):
40+
return create_service_principal_keytab( customer, "host", keytabfilename)
41+
42+
def create_service_principal_keytab( customer, service, keytabfilename ):
43+
44+
result = False
45+
realm = _get_realm_name( customer )
46+
admin_principal_name = _get_realm_admin_princ_name(customer)
47+
admin_cred = _get_realm_admin_princ_cred(customer)
48+
49+
kadm_sess = _get_kadm_session (admin_principal_name, admin_cred)
50+
51+
srv_princ_name = _get_principal_name(realm, service)
52+
53+
if not _principal_exists(kadm_sess, srv_princ_name):
54+
_create_principal(kadm_sess, srv_princ_name)
55+
56+
srv_principal = _get_principal(kadm_sess, srv_princ_name)
57+
58+
if srv_principal is not None:
59+
result = _kadmin_ktadd(kadm_sess, srv_principal.principal, keytabfilename )
60+
61+
logging.info("keytab file %s created", keytabfilename)
62+
63+
return result;
64+
65+
66+
### get param values from SSM
67+
def _get_ssm_param_value ( param_name, decrypt_flag ):
68+
ssmClient = boto3.client("ssm")
69+
response = ssmClient.get_parameter(Name = param_name, WithDecryption = decrypt_flag );
70+
71+
return response["Parameter"]["Value"]
72+
73+
## use socket.getfqdn to build host principal name
74+
def _get_principal_name(realm, prefix = None):
75+
if prefix is None:
76+
prefix = "host";
77+
78+
host_fqdn = socket.getfqdn()
79+
return prefix + "/" + host_fqdn + "@" + realm
80+
81+
## get kadm session
82+
def _get_kadm_session( user_name, user_cred ):
83+
return kadmin.init_with_password(user_name, user_cred)
84+
85+
## get realm from SSM key prefixed by iai_realm_name
86+
def _get_realm_name( customer = None ):
87+
if customer is None:
88+
customer = "corp-iai"
89+
90+
sparam = customer + "-realm-name"
91+
92+
return _get_ssm_param_value( sparam, True )
93+
94+
## get realm admin name from SSM key prefixed by iai_realm_amdin_principal_
95+
## This does not need to be full admin - but should be able to create
96+
## principals, look up principals and export keys (so nearly a full admin)
97+
def _get_realm_admin_princ_name( customer = None ):
98+
if customer is None:
99+
customer = "corp-iai"
100+
101+
sparam = customer + "-realm-principal"
102+
103+
return _get_ssm_param_value( sparam, True )
104+
105+
## get realm admin credential value - this is a password for now
106+
## but it could be a base64 keytab in the future
107+
def _get_realm_admin_princ_cred( customer = None ):
108+
if customer is None:
109+
customer = "corp-iai"
110+
111+
sparam = customer + "-realm-principal-creds"
112+
113+
return _get_ssm_param_value( sparam, True )
114+
115+
## Returns the specifically named principal
116+
def _get_principal ( kadm_sess, principal_name ):
117+
if kadm_sess is not None:
118+
principal = kadm_sess.getprinc(principal_name)
119+
return principal
120+
else:
121+
return None #TODO: Throw here!
122+
123+
## Checks if the principal exists
124+
def _principal_exists( kadm_sess, principal_name ):
125+
exists = False
126+
if kadm_sess is not None:
127+
exists = kadm_sess.principal_exists(principal_name)
128+
129+
return exists
130+
131+
## Creates principal - throws DuplicateError if principal exists
132+
def _create_principal( kadm_sess, princ_name ):
133+
created = False
134+
if kadm_sess is not None:
135+
try:
136+
created = kadm_sess.add_principal(princ_name, None)
137+
except kadmin.DuplicateError:
138+
created = True
139+
140+
return created
141+
142+
## Creates Keytab file - uses enhanced python-kadmin library
143+
## github.com/integrateai/python-kadmin
144+
## forked from rjancewicz/python-kadmin
145+
## and enhanced with very based ktadd functionality
146+
##
147+
def _kadmin_ktadd( kadm_sess, princ_name, ktname ):
148+
created = False
149+
if kadm_sess is not None:
150+
created = kadm_sess.ktadd(princ_name, ktname)
151+
152+
return created

pykerberize/test/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

pykerberize/test/test_pykerberize.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from pykerberize import pykerberize
2+
3+
def test_get_ssm_param_value( ):
4+
assert pykerberize._get_ssm_param_value("corp-iai-domain-name", True) == "corp.integrateai.net"
5+
6+
def test_get_host_principal_name( ):
7+
assert pykerberize._get_principal_name("host","@CORP.INTEGRATEAI.NET") is not None
8+
9+
def test_get_kadm_session( ):
10+
assert pykerberize._get_kadm_session("admin@CORP.INTEGRATEAI.NET", "bordercollie") is not None
11+
12+
def test_get_principal( ):
13+
sess = pykerberize._get_kadm_session("admin@CORP.INTEGRATEAI.NET", "bordercollie")
14+
assert sess is not None
15+
16+
principal = pykerberize._get_principal(sess, "host/test.corp.integrateai.net@CORP.INTEGRATEAI.NET")
17+
assert principal is not None
18+
19+
principal = pykerberize._get_principal(sess, "madeup@CORP.INTEGRATEAI.NET")
20+
assert principal is None
21+
22+
def test_get_realm_name( ):
23+
assert pykerberize._get_realm_name() is not None
24+
25+
def test_principal_exists( ):
26+
sess = pykerberize._get_kadm_session("admin@CORP.INTEGRATEAI.NET", "bordercollie")
27+
assert sess is not None
28+
29+
exists = pykerberize._principal_exists(sess, "host/test.corp.integrateai.net@CORP.INTEGRATEAI.NET")
30+
assert exists == True
31+
32+
exists = pykerberize._principal_exists(sess, "FakeId@CORP.INTEGRATEAI.NET")
33+
assert exists == False
34+
35+
def test_create_principal( ):
36+
sess = pykerberize._get_kadm_session("admin@CORP.INTEGRATEAI.NET", "bordercollie")
37+
assert sess is not None
38+
39+
created = pykerberize._create_principal(sess,"host/test.corp.integrateai.net@CORP.INTEGRATEAI.NET")
40+
assert created == True
41+
42+
def test_kadmin_ktadd( ):
43+
sess = pykerberize._get_kadm_session("admin@CORP.INTEGRATEAI.NET", "bordercollie")
44+
assert sess is not None
45+
46+
kt = pykerberize._kadmin_ktadd(sess, "host/test.corp.integrateai.net@CORP.INTEGRATEAI.NET", "FILE:/root/pytest.keytab")
47+
assert kt
48+
49+

0 commit comments

Comments
 (0)