Skip to content

Commit 778aa7e

Browse files
authored
Merge pull request #1 from jneprz/sftp
Initial SFTP Extension
2 parents 31e0eb9 + 1b461f8 commit 778aa7e

32 files changed

+4366
-0
lines changed

src/sftp/HISTORY.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.. :changelog:
2+
3+
Release History
4+
===============
5+
6+
1.0.0b1
7+
+++++++
8+
* Initial preview release with SFTP connection and certificate generation support.

src/sftp/README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Azure CLI SFTP Commands
2+
========================
3+
4+
Secure connections to Azure Storage via SFTP with SSH certificates.
5+
6+
Commands include certificate generation and SFTP connection management.

src/sftp/azext_sftp/__init__.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
"""
7+
Azure CLI SFTP Extension
8+
9+
This extension provides secure SFTP connectivity to Azure Storage Accounts
10+
with automatic Azure AD authentication and certificate management.
11+
12+
Key Features:
13+
- Fully managed SSH certificate generation using Azure AD
14+
- Support for existing SSH keys and certificates
15+
- Interactive and batch SFTP operations
16+
- Automatic credential cleanup for security
17+
- Integration with Azure Storage SFTP endpoints
18+
19+
Commands:
20+
- az sftp cert: Generate SSH certificates for SFTP authentication
21+
- az sftp connect: Connect to Azure Storage Account via SFTP
22+
"""
23+
24+
from azure.cli.core import AzCommandsLoader
25+
26+
from azext_sftp._help import helps # pylint: disable=unused-import
27+
28+
29+
class SftpCommandsLoader(AzCommandsLoader):
30+
"""Command loader for the SFTP extension."""
31+
32+
def __init__(self, cli_ctx=None):
33+
from azure.cli.core.commands import CliCommandType
34+
from azext_sftp._client_factory import cf_sftp
35+
36+
sftp_custom = CliCommandType(
37+
operations_tmpl='azext_sftp.custom#{}',
38+
client_factory=cf_sftp)
39+
40+
super(SftpCommandsLoader, self).__init__(
41+
cli_ctx=cli_ctx,
42+
custom_command_type=sftp_custom)
43+
44+
def load_command_table(self, args):
45+
"""Load the command table for SFTP commands."""
46+
from azext_sftp.commands import load_command_table
47+
load_command_table(self, args)
48+
return self.command_table
49+
50+
def load_arguments(self, command):
51+
"""Load arguments for SFTP commands."""
52+
from azext_sftp._params import load_arguments
53+
load_arguments(self, command)
54+
55+
56+
COMMAND_LOADER_CLS = SftpCommandsLoader
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
def cf_sftp(cli_ctx, *_): # pylint: disable=unused-argument
7+
"""
8+
Client factory for SFTP extension.
9+
This extension doesn't require a specific Azure management client
10+
as it operates using SSH/SFTP protocols directly.
11+
"""
12+
return None

src/sftp/azext_sftp/_help.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for license information.
5+
# --------------------------------------------------------------------------------------------
6+
7+
from knack.help_files import helps # pylint: disable=unused-import
8+
9+
10+
helps['sftp'] = """
11+
type: group
12+
short-summary: Commands to connect to Azure Storage Accounts via SFTP
13+
long-summary: |
14+
These commands allow you to generate certificates and connect to Azure Storage Accounts using SFTP.
15+
16+
PREREQUISITES:
17+
- Azure Storage Account with SFTP enabled
18+
- Appropriate RBAC permissions (Storage Blob Data Contributor or similar)
19+
- Azure CLI authentication (az login)
20+
- Network connectivity to Azure Storage endpoints
21+
22+
The SFTP extension provides two main capabilities:
23+
1. Certificate generation using Azure AD authentication (similar to 'az ssh cert')
24+
2. Fully managed SFTP connections to Azure Storage with automatic credential handling
25+
26+
AUTHENTICATION MODES:
27+
- Fully managed: No credentials needed - automatically generates SSH certificate
28+
- Certificate-based: Use existing SSH certificate file
29+
- Key-based: Use SSH public/private key pair (generates certificate automatically)
30+
31+
This extension closely follows the patterns established by the SSH extension.
32+
"""
33+
34+
helps['sftp cert'] = """
35+
type: command
36+
short-summary: Generate SSH certificate for SFTP authentication
37+
long-summary: |
38+
Generate an SSH certificate that can be used for authenticating to Azure Storage SFTP endpoints.
39+
This uses Azure AD authentication to generate a certificate similar to 'az ssh cert'.
40+
41+
CERTIFICATE NAMING:
42+
- Generated certificates have '-aadcert.pub' suffix (e.g., id_rsa-aadcert.pub)
43+
- Certificates are valid for a limited time (typically 1 hour)
44+
- Private keys are generated with 'id_rsa' name when key pair is created
45+
46+
The certificate can be used with 'az sftp connect' or with standard SFTP clients.
47+
examples:
48+
- name: Generate a certificate using an existing public key
49+
text: az sftp cert --public-key-file ~/.ssh/id_rsa.pub --file ~/my_cert.pub
50+
- name: Generate a certificate and create a new key pair in the same directory
51+
text: az sftp cert --file ~/my_cert.pub
52+
- name: Generate a certificate with custom SSH client folder
53+
text: az sftp cert --file ~/my_cert.pub --ssh-client-folder "C:\\Program Files\\OpenSSH"
54+
"""
55+
56+
helps['sftp connect'] = """
57+
type: command
58+
short-summary: Connect to Azure Storage Account via SFTP
59+
long-summary: |
60+
Establish an SFTP connection to an Azure Storage Account.
61+
62+
AUTHENTICATION MODES:
63+
1. Fully managed (RECOMMENDED): Run without credentials - automatically generates SSH certificate
64+
and establishes connection. Credentials are cleaned up after use.
65+
66+
2. Certificate-based: Use existing SSH certificate file. Certificate must be generated with
67+
'az sftp cert' or compatible with Azure AD authentication.
68+
69+
3. Key-based: Provide SSH keys - command will generate certificate automatically from your keys.
70+
71+
CONNECTION DETAILS:
72+
- Username format: {storage-account}.{azure-username}
73+
- Port: Uses SSH default (typically 22) unless specified with --port
74+
- Endpoints resolved automatically based on Azure cloud environment:
75+
* Azure Public: {storage-account}.blob.core.windows.net
76+
* Azure China: {storage-account}.blob.core.chinacloudapi.cn
77+
* Azure Government: {storage-account}.blob.core.usgovcloudapi.net
78+
79+
SECURITY:
80+
- Generated credentials are automatically cleaned up after connection
81+
- Temporary files stored in secure temporary directories
82+
- Certificate validity is checked and renewed if expired
83+
examples:
84+
- name: Connect with automatic certificate generation (fully managed - RECOMMENDED)
85+
text: az sftp connect --storage-account mystorageaccount
86+
- name: Connect to storage account with existing certificate
87+
text: az sftp connect --storage-account mystorageaccount --certificate-file ~/my_cert.pub
88+
- name: Connect with existing SSH key pair
89+
text: az sftp connect --storage-account mystorageaccount --public-key-file ~/.ssh/id_rsa.pub --private-key-file ~/.ssh/id_rsa
90+
- name: Connect with custom port
91+
text: az sftp connect --storage-account mystorageaccount --port 2222
92+
- name: Connect with additional SFTP arguments for debugging
93+
text: az sftp connect --storage-account mystorageaccount --sftp-args "-v"
94+
- name: Connect with custom SSH client folder (Windows)
95+
text: az sftp connect --storage-account mystorageaccount --ssh-client-folder "C:\\Program Files\\OpenSSH"
96+
- name: Run batch commands after connecting
97+
text: az sftp connect --storage-account mystorageaccount --batch-commands "ls\\nget file.txt\\nbye"
98+
"""

src/sftp/azext_sftp/_params.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
# pylint: disable=line-too-long
6+
7+
8+
def load_arguments(self, _):
9+
10+
with self.argument_context('sftp cert') as c:
11+
c.argument('cert_path', options_list=['--file', '-f'],
12+
help='The file path to write the SSH cert to, defaults to public key path with -aadcert.pub appended')
13+
c.argument('public_key_file', options_list=['--public-key-file', '-p'],
14+
help='The RSA public key file path. If not provided, '
15+
'generated key pair is stored in the same directory as --file.')
16+
c.argument('ssh_client_folder', options_list=['--ssh-client-folder'],
17+
help='Folder path that contains ssh executables (ssh-keygen, ssh). '
18+
'Default to ssh executables in your PATH or C:\\Windows\\System32\\OpenSSH on Windows.')
19+
20+
with self.argument_context('sftp connect') as c:
21+
c.argument('storage_account', options_list=['--storage-account', '-s'],
22+
help='Azure Storage Account name for SFTP connection. Must have SFTP enabled.')
23+
c.argument('port', options_list=['--port'],
24+
help='SFTP port. If not specified, uses SSH default port (typically 22).',
25+
type=int)
26+
c.argument('cert_file', options_list=['--certificate-file', '-c'],
27+
help='Path to SSH certificate file for authentication. '
28+
'Must be generated with "az sftp cert" or compatible Azure AD certificate. '
29+
'If not provided, certificate will be generated automatically.')
30+
c.argument('private_key_file', options_list=['--private-key-file', '-i'],
31+
help='Path to RSA private key file. If provided without certificate, '
32+
'a certificate will be generated automatically from this key.')
33+
c.argument('public_key_file', options_list=['--public-key-file', '-p'],
34+
help='Path to RSA public key file. If provided without certificate, '
35+
'a certificate will be generated automatically from this key.')
36+
c.argument('sftp_args', options_list=['--sftp-args'],
37+
help='Additional arguments to pass to the SFTP client. '
38+
'Example: "-v" for verbose output, "-o ConnectTimeout=30" for custom timeout.')
39+
c.argument('ssh_client_folder', options_list=['--ssh-client-folder'],
40+
help='Path to folder containing SSH client executables (ssh, sftp, ssh-keygen). '
41+
'Default: Uses executables from PATH or C:\\Windows\\System32\\OpenSSH on Windows.')
42+
c.argument('sftp_batch_commands', options_list=['--batch-commands'],
43+
help='SFTP batch commands to execute after connecting (non-interactive mode). '
44+
'Separate commands with \\n. Example: "ls\\nget file.txt\\nbye"')

src/sftp/azext_sftp/_validators.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from azure.cli.core import azclierror
7+
from azure.cli.core.commands.client_factory import get_subscription_id
8+
from msrestazure.tools import is_valid_resource_id, resource_id
9+
10+
11+
def storage_account_name_or_id_validator(cmd, namespace):
12+
"""
13+
Validator for storage account name or resource ID.
14+
Converts storage account name to full resource ID if needed.
15+
"""
16+
if namespace.storage_account:
17+
if not is_valid_resource_id(namespace.storage_account):
18+
if not hasattr(namespace, 'resource_group_name') or not namespace.resource_group_name:
19+
raise azclierror.RequiredArgumentMissingError(
20+
"When providing storage account name, --resource-group is required. "
21+
"Alternatively, provide the full resource ID."
22+
)
23+
namespace.storage_account = resource_id(
24+
subscription=get_subscription_id(cmd.cli_ctx),
25+
resource_group=namespace.resource_group_name,
26+
namespace='Microsoft.Storage',
27+
type='storageAccounts',
28+
name=namespace.storage_account
29+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"azext.isPreview": true,
3+
"azext.minCliCoreVersion": "2.67.0",
4+
"azext.maxCliCoreVersion": "2.99.0"
5+
}

src/sftp/azext_sftp/commands.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
"""
7+
Command definitions for the Azure CLI SFTP extension.
8+
9+
This module defines the available SFTP commands and their routing
10+
to the appropriate custom functions.
11+
"""
12+
13+
14+
def load_command_table(self, _):
15+
"""
16+
Load command table for SFTP extension.
17+
18+
Commands:
19+
- sftp cert: Generate SSH certificates for SFTP authentication
20+
- sftp connect: Connect to Azure Storage Account via SFTP
21+
"""
22+
with self.command_group('sftp') as g:
23+
g.custom_command('cert', 'sftp_cert')
24+
g.custom_command('connect', 'sftp_connect')
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
import json
7+
import base64
8+
9+
from knack import log
10+
11+
logger = log.get_logger(__name__)
12+
13+
14+
def format_relay_info_string(relay_info):
15+
relay_info_string = json.dumps(
16+
{
17+
"relay": {
18+
"namespaceName": relay_info['namespaceName'],
19+
"namespaceNameSuffix": relay_info['namespaceNameSuffix'],
20+
"hybridConnectionName": relay_info['hybridConnectionName'],
21+
"accessKey": relay_info['accessKey'],
22+
"expiresOn": relay_info['expiresOn'],
23+
"serviceConfigurationToken": relay_info['serviceConfigurationToken']
24+
}
25+
})
26+
result_bytes = relay_info_string.encode("ascii")
27+
enc = base64.b64encode(result_bytes)
28+
base64_result_string = enc.decode("ascii")
29+
return base64_result_string

0 commit comments

Comments
 (0)