Skip to content

Commit 9165fbb

Browse files
author
mstanescu_adobe
committed
[AAM-65858] Add ops-cli Teleport support
1 parent 8246f18 commit 9165fbb

File tree

13 files changed

+419
-196
lines changed

13 files changed

+419
-196
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/
762762
763763
## Running tests
764764
765-
- on your machine: `py.test tests`
765+
- on your machine: `python -m pytest tests` or `build_scripts/run_tests.sh`
766766
767767
# Troubleshooting
768768

src/ops/cli/parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,7 @@ def configure_common_ansible_args(parser):
112112
parser.add_argument('--noscb', action='store_false', dest='use_scb',
113113
help='Disable use of Shell Control Box (SCB) even if '
114114
'it is enabled in the cluster config')
115+
parser.add_argument('--teleport', action='store_false', dest='use_teleport',
116+
help='Use Teleport for SSH')
115117

116118
return parser

src/ops/cli/playbook.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ def __init__(self, ops_config, root_dir, inventory_generator,
7373
def run(self, args, extra_args):
7474
logger.info("Found extra_args %s", extra_args)
7575
inventory_path, ssh_config_paths = self.inventory_generator.generate()
76-
ssh_config_path = SshConfigGenerator.get_ssh_config_path(self.cluster_config,
76+
ssh_config_generator = SshConfigGenerator(self.ops_config.package_dir)
77+
ssh_config_path = ssh_config_generator.get_ssh_config_path(self.cluster_config,
7778
ssh_config_paths,
78-
args.use_scb)
79+
args)
80+
7981
ssh_config = f"ANSIBLE_SSH_ARGS='-F {ssh_config_path}'"
8082

8183
ansible_config = "ANSIBLE_CONFIG=%s" % self.ops_config.ansible_config_path

src/ops/cli/run.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ def run(self, args, extra_args):
6868
logger.info("Found extra_args %s", extra_args)
6969
inventory_path, ssh_config_paths = self.inventory_generator.generate()
7070
limit = args.host_pattern
71-
ssh_config_path = SshConfigGenerator.get_ssh_config_path(self.cluster_config,
71+
ssh_config_generator = SshConfigGenerator(self.ops_config.package_dir)
72+
ssh_config_path = ssh_config_generator.get_ssh_config_path(self.cluster_config,
7273
ssh_config_paths,
73-
args.use_scb)
74+
args)
7475
extra_args = ' '.join(args.extra_args)
7576
command = """cd {root_dir}
7677
ANSIBLE_SSH_ARGS='-F {ssh_config}' ANSIBLE_CONFIG={ansible_config_path} ansible -i {inventory_path} '{limit}' \\

src/ops/cli/ssh.py

Lines changed: 236 additions & 116 deletions
Large diffs are not rendered by default.

src/ops/cli/sync.py

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@ def configure(self, parser):
3131
parser.add_argument('--noscb', action='store_false', dest='use_scb',
3232
help='Disable use of Shell Control Box (SCB) '
3333
'even if it is enabled in the cluster config')
34+
parser.add_argument(
35+
'--teleport',
36+
action='store_false',
37+
dest='use_teleport',
38+
help='Use Teleport for SSH')
3439
parser.add_argument(
3540
'opts',
36-
default=['-va --progress'],
3741
nargs='*',
38-
help='Rsync opts')
42+
help='Sync opts')
3943

4044
def get_help(self):
4145
return 'Sync files from/to a cluster'
@@ -54,6 +58,9 @@ def get_epilog(self):
5458
5559
# extra rsync options
5660
ops cluster.yml sync 'dcs[0]:/usr/local/demdex/conf' /tmp/configurator-data -l remote_user -- --progress
61+
62+
# extra sync option for Teleport (recursive download, quiet, port)
63+
ops cluster.yml sync 'dcs[0]:/usr/local/demdex/conf' /tmp/configurator-data -- --recursive/port/quiet
5764
"""
5865

5966

@@ -73,29 +80,36 @@ def __init__(self, cluster_config, root_dir,
7380

7481
def run(self, args, extra_args):
7582
logger.info("Found extra_args %s", extra_args)
76-
inventory_path, ssh_config_paths = self.inventory_generator.generate()
83+
7784
src = PathExpr(args.src)
7885
dest = PathExpr(args.dest)
86+
remote = self.get_remote(dest, src)
7987

80-
ssh_config_path = SshConfigGenerator.get_ssh_config_path(self.cluster_config,
81-
ssh_config_paths,
82-
args.use_scb)
8388
if src.is_remote and dest.is_remote:
8489
display(
85-
'Too remote expressions are not allowed',
90+
'Two remote expressions are not allowed',
8691
stderr=True,
8792
color='red')
8893
return
8994

90-
if src.is_remote:
91-
remote = src
92-
else:
93-
remote = dest
94-
9595
display(
9696
"Looking for hosts for pattern '%s'" %
9797
remote.pattern, stderr=True)
9898

99+
if self.is_teleport_enabled(args):
100+
command = self.execute_teleport_scp(args, src, dest)
101+
else:
102+
ssh_user = self.cluster_config.get('ssh_user') or self.ops_config.get('ssh.user') or getpass.getuser()
103+
if remote.remote_user:
104+
ssh_user = remote.remote_user
105+
elif args.user:
106+
ssh_user = args.user
107+
ssh_host = self.populate_remote_hosts(remote)[0]
108+
command = self.execute_rsync_scp(args, src, dest, ssh_user, ssh_host, self.get_ssh_config_path(args))
109+
110+
return dict(command=command)
111+
112+
def populate_remote_hosts(self, remote):
99113
remote_hosts = []
100114
hosts = self.ansible_inventory.get_hosts(remote.pattern)
101115
if not hosts:
@@ -106,28 +120,43 @@ def run(self, args, extra_args):
106120
for host in hosts:
107121
ssh_host = host.get_vars().get('ansible_ssh_host') or host
108122
remote_hosts.append(ssh_host)
123+
return remote_hosts
109124

110-
for ssh_host in remote_hosts:
111-
ssh_user = self.cluster_config.get('ssh_user') or self.ops_config.get(
112-
'ssh.user') or getpass.getuser()
113-
if remote.remote_user:
114-
ssh_user = remote.remote_user
115-
elif args.user:
116-
ssh_user = args.user
117-
118-
from_path = src.with_user_and_path(ssh_user, ssh_host)
119-
to_path = dest.with_user_and_path(ssh_user, ssh_host)
120-
121-
command = 'rsync {opts} {from_path} {to_path} -e "ssh -F {ssh_config}"'.format(
122-
opts=" ".join(args.opts),
123-
from_path=from_path,
124-
to_path=to_path,
125-
ssh_config=ssh_config_path
126-
127-
)
128-
129-
return dict(command=command)
130-
125+
def get_remote(self, dest, src):
126+
if src.is_remote:
127+
remote = src
128+
else:
129+
remote = dest
130+
return remote
131+
132+
def get_ssh_config_path(self, args):
133+
ssh_config_generator = SshConfigGenerator(self.ops_config.package_dir)
134+
_, ssh_config_paths = self.inventory_generator.generate()
135+
return ssh_config_generator.get_ssh_config_path(self.cluster_config,
136+
ssh_config_paths,
137+
138+
args)
139+
140+
def execute_teleport_scp(self, args, src, dest):
141+
return 'tsh scp {opts} {from_path} {to_path}'.format(
142+
from_path=src,
143+
to_path=dest,
144+
opts=" ".join(args.opts)
145+
)
146+
147+
def execute_rsync_scp(self, args, src, dest, ssh_user, ssh_host, ssh_config_path):
148+
from_path = src.with_user_and_path(ssh_user, ssh_host)
149+
to_path = dest.with_user_and_path(ssh_user, ssh_host)
150+
return 'rsync {opts} {from_path} {to_path} -e "ssh -F {ssh_config}"'.format(
151+
opts=" ".join(args.opts),
152+
from_path=from_path,
153+
to_path=to_path,
154+
ssh_config=ssh_config_path
155+
)
156+
157+
158+
def is_teleport_enabled(self, args):
159+
return True if self.cluster_config.get('teleport', {}).get('enabled') and args.use_teleport else False
131160

132161
class PathExpr(object):
133162

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# teleport.cfg
2+
Host *
3+
ProxyCommand "/usr/local/bin/tsh" proxy ssh --cluster=teleport-prod --proxy=teleport.adobe.net:443 %r@%n:%p
4+
UserKnownHostsFile /dev/null
5+
StrictHostKeyChecking no
6+
ControlMaster auto
7+
ControlPersist 600s
8+
User {ssh_username}
9+
Port 22

src/ops/inventory/ec2inventory.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,21 @@
1717

1818

1919
class Ec2Inventory(object):
20+
21+
2022
@staticmethod
2123
def _empty_inventory():
2224
return {"_meta": {"hostvars": {}}}
2325

24-
def __init__(self, boto_profile, regions, filters=None, bastion_filters=None):
26+
def __init__(self, boto_profile, regions, filters=None, bastion_filters=None, teleport_enabled=False):
2527

2628
self.filters = filters or []
2729
self.regions = regions.split(',')
2830
self.boto_profile = boto_profile
2931
self.bastion_filters = bastion_filters or []
3032
self.group_callbacks = []
3133
self.boto3_session = self.create_boto3_session(boto_profile)
34+
self.teleport_enabled = teleport_enabled
3235

3336
# Inventory grouped by instance IDs, tags, security groups, regions,
3437
# and availability zones
@@ -145,7 +148,11 @@ def add_instance(self, bastion_ip, instance, region):
145148
if not dest:
146149
return
147150

148-
if bastion_ip and bastion_ip != instance.get('PublicIpAddress'):
151+
if self.teleport_enabled:
152+
ansible_ssh_host = self.get_tag_value(instance, ['hostname','CMDB_hostname','Adobe:FQDN'])
153+
if not ansible_ssh_host:
154+
ansible_ssh_host = instance.get('PrivateIpAddress')
155+
elif bastion_ip and bastion_ip != instance.get('PublicIpAddress'):
149156
ansible_ssh_host = bastion_ip + "--" + instance.get('PrivateIpAddress')
150157
elif instance.get('PublicIpAddress'):
151158
ansible_ssh_host = instance.get('PublicIpAddress')
@@ -183,6 +190,13 @@ def add_instance(self, bastion_ip, instance, region):
183190
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
184191
self.inventory["_meta"]["hostvars"][dest]['ansible_ssh_host'] = ansible_ssh_host
185192

193+
def get_tag_value(self, instance, tag_keys):
194+
for tag_key in tag_keys:
195+
for tag in instance.get('Tags', []):
196+
if tag['Key'] == tag_key:
197+
return tag['Value']
198+
return None
199+
186200
def get_host_info_dict_from_instance(self, instance):
187201
instance_vars = {}
188202
for key, value in instance.items():

src/ops/inventory/plugin/cns.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def cns(args):
2626
jsn = ec2(dict(
2727
region=region,
2828
boto_profile=profile,
29+
teleport_enabled=args.get('teleport_enabled', False),
2930
cache=args.get('cache', 3600 * 24),
3031
filters=[
3132
{'Name': 'tag:cluster', 'Values': [cns_cluster]}

src/ops/inventory/plugin/ec2.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
def ec2(args):
1515
filters = args.get('filters', [])
1616
bastion_filters = args.get('bastion', [])
17+
teleport_enabled = args.get('teleport_enabled')
1718

1819
if args.get('cluster') and not args.get('filters'):
1920
filters = [{'Name': 'tag:cluster', 'Values': [args.get('cluster')]}]
@@ -27,4 +28,6 @@ def ec2(args):
2728
return Ec2Inventory(boto_profile=args['boto_profile'],
2829
regions=args['region'],
2930
filters=filters,
30-
bastion_filters=bastion_filters).get_as_json()
31+
bastion_filters=bastion_filters,
32+
teleport_enabled=teleport_enabled
33+
).get_as_json()

0 commit comments

Comments
 (0)