10000 Create an example for Service Account SSH using OS Login. (#1793) · devlance/python-docs-samples@09801e4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 09801e4

Browse files
dwightjlengelke
authored andcommitted
Create an example for Service Account SSH using OS Login. (GoogleCloudPlatform#1793)
* Create an example of how to use the OS Login API to manage SSH keys for a service account In some situations you might want a Compute Engine service account to be able to SSH to instances over an internal VPC network to run automated tasks. If your project uses OS Login, your service account can generate a key pair for itself and apply the public key as an authorized key on its account. With OS Login enabled on a project or an instance, the instance can access the public key, grant SSH access to the service account, and allow the service account to run remote commands over SSH. * Linter fixes. * More linter fixes. * More linter fixes. * Create a test for the OS Login service account example. * Rename service-account-ssh.py to service_account_ssh.py * Update service_account_ssh_test.py * Update service_account_ssh.py * Update service_account_ssh_test.py * Update service_account_ssh.py * Update service_account_ssh_test.py * Update service_account_ssh_test.py * Update service_account_ssh.py * Update service_account_ssh_test.py * Create requirements.txt * Update service_account_ssh.py Redesigned a few methods to facilitate testing. * Update service_account_ssh_test.py Redesigned the test application to properly handle OS Login credentials for the service account outside of a Compute Engine instance. On a Compute Engine instance, the service account has easy access to its own credentials. Outside of a Compute Engine instance, you must obtain the service account key. Additionally, the test now forms its own OS Login service account address rather than obtaining it from metadata. * Update service_account_ssh.py * Update service_account_ssh_test.py * Update service_account_ssh.py * Update service_account_ssh_test.py * Update service_account_ssh_test.py * Update service_account_ssh.py * Formatting improvements. * Formatting improvements. * Update service_account_ssh.py * Update service_account_ssh_test.py * Update service_account_ssh.py * Update service_account_ssh_test.py
1 parent 1980f31 commit 09801e4

File tree

3 files changed

+419
-0
lines changed

3 files changed

+419
-0
lines changed

compute/oslogin/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
google-api-python-client==1.7.4
2+
google-auth==1.6.1
3+
google-auth-httplib2==0.0.3
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2018 Google Inc. All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""Example of using the OS Login API to apply public SSH keys for a service
18+
account, and use that service account to execute commands on a remote
19+
instance over SSH. This example uses zonal DNS names to address instances
20+
on the same internal VPC network.
21+
"""
22+
23+
# [START imports_and_variables]
24+
import time
25+
import subprocess
26+
import uuid
27+
import logging
28+
import requests
29+
import argparse
30+
import googleapiclient.discovery
31+
32+
# Global variables
33+
SERVICE_ACCOUNT_METADATA_URL = (
34+
'http://metadata.google.internal/computeMetadata/v1/instance/'
35+
'service-accounts/default/email')
36+
HEADERS = {'Metadata-Flavor': 'Google'}
37+
38+
# [END imports_and_variables]
39+
40+
41+
# [START run_command_local]
42+
def execute(cmd, cwd=None, capture_output=False, env=None, raise_errors=True):
43+
"""Execute an external command (wrapper for Python subprocess)."""
44+
logging.info('Executing command: {cmd}'.format(cmd=str(cmd)))
45+
stdout = subprocess< 10000 /span>.PIPE if capture_output else None
46+
process = subprocess.Popen(cmd, cwd=cwd, env=env, stdout=stdout)
47+
output = process.communicate()[0]
48+
returncode = process.returncode
49+
if returncode:
50+
# Error
51+
if raise_errors:
52+
raise subprocess.CalledProcessError(returncode, cmd)
53+
else:
54+
logging.info('Command returned error status %s', returncode)
55+
if output:
56+
logging.info(output)
57+
return returncode, output
58+
# [END run_command_local]
59+
60+
61+
# [START create_key]
62+
def create_ssh_key(oslogin, account, private_key_file=None, expire_time=300):
63+
"""Generate an SSH key pair and apply it to the specified account."""
64+
private_key_file = private_key_file or '/tmp/key-' + str(uuid.uuid4())
65+
execute(['ssh-keygen', '-t', 'rsa', '-N', '', '-f', private_key_file])
66+
67+
with open(private_key_file + '.pub', 'r') as original:
68+
public_key = original.read().strip()
69+
70+
# Expiration time is in microseconds.
71+
expiration = int((time.time() + expire_time) * 1000000)
72+
73+
body = {
74+
'key': public_key,
75+
'expirationTimeUsec': expiration,
76+
}
77+
oslogin.users().importSshPublicKey(parent=account, body=body).execute()
78+
return private_key_file
79+
# [END create_key]
80+
81+
82+
# [START run_command_remote]
83+
def run_ssh(cmd, private_key_file, username, hostname):
84+
"""Run a command on a remote system."""
85+
ssh_command = [
86+
'ssh', '-i', private_key_file, '-o', 'StrictHostKeyChecking=no',
87+
'{username}@{hostname}'.format(username=username, hostname=hostname),
88+
cmd,
89+
]
90+
ssh = subprocess.Popen(
91+
ssh_command, shell=False, stdout=subprocess.PIPE,
92+
stderr=subprocess.PIPE)
93+
result = ssh.stdout.readlines()
94+
return result if result else ssh.stderr.readlines()
95+
96+
# [END run_command_remote]
97+
98+
99+
# [START main]
100+
def main(
101+
cmd, project, instance, zone, oslogin=None, account=None, hostname=None
102+
):
103+
"""Run a command on a remote system."""
104+
105+
# Create the OS Login API object.
106+
oslogin = oslogin or googleapiclient.discovery.build('oslogin', 'v1')
107+
108+
# Identify the service account ID if it is not already provided.
109+
account = account or 'users/' + requests.get(
110+
SERVICE_ACCOUNT_METADATA_URL, headers=HEADERS).text
111+
112+
# Create a new SSH key pair and associate it with the service account.
113+
private_key_file = create_ssh_key(oslogin, account)
114+
115+
# Using the OS Login API, get the POSIX user name from the login profile
116+
# for the service account.
117+
profile = oslogin.users().getLoginProfile(name=account).execute()
118+
username = profile.get('posixAccounts')[0].get('username')
119+
120+
# Create the hostname of the target instance using the instance name,
121+
# the zone where the instance is located, and the project that owns the
122+
# instance.
123+
hostname = hostname or '{instance}.{zone}.c.{project}.internal'.format(
124+
instance=instance, zone=zone, project=project)
125+
126+
# Run a command on the remote instance over SSH.
127+
result = run_ssh(cmd, private_key_file, username, hostname)
128+
129+
# Print the command line output from the remote instance.
130+
# Use .rstrip() rather than end='' for Python 2 compatability.
131+
for line in result:
132+
print(line.decode('utf-8').rstrip('\n\r'))
133+
134+
# Shred the private key and delete the pair.
135+
execute(['shred', private_key_file])
136+
execute(['rm', private_key_file])
137+
execute(['rm', private_key_file + '.pub'])
138+
139+
140+
if __name__ == '__main__':
141+
142+
parser = argparse.ArgumentParser(
143+
description=__doc__,
144+
formatter_class=argparse.RawDescriptionHelpFormatter)
145+
parser.add_argument(
146+
'--cmd', default='uname -a',
147+
help='The command to run on the remote instance.')
148+
parser.add_argument(
149+
'--project', default='my-project-id',
150+
help='Your Google Cloud project ID.')
151+
parser.add_argument(
152+
'--zone', default='us-central1-f',
153+
help='The zone where the target instance is locted.')
154+
parser.add_argument(
155+
'--instance', default='my-instance-name',
156+
help='The target instance for the ssh command.')
157+
args = parser.parse_args()
158+
159+
main(args.cmd, args.project, args.instance, args.zone)
160+
161+
# [END main]

0 commit comments

Comments
 (0)
0