Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions src/sftp/HISTORY.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. :changelog:

Release History
===============

0.1.0
++++++
* Initial release.
5 changes: 5 additions & 0 deletions src/sftp/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Microsoft Azure CLI 'sftp' Extension
==========================================

This package is for the 'sftp' extension.
i.e. 'az sftp'
56 changes: 56 additions & 0 deletions src/sftp/azext_sftp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""
Azure CLI SFTP Extension

This extension provides secure SFTP connectivity to Azure Storage Accounts
with automatic Azure AD authentication and certificate management.

Key Features:
- Fully managed SSH certificate generation using Azure AD
- Support for existing SSH keys and certificates
- Interactive and batch SFTP operations
- Automatic credential cleanup for security
- Integration with Azure Storage SFTP endpoints

Commands:
- az sftp cert: Generate SSH certificates for SFTP authentication
- az sftp connect: Connect to Azure Storage Account via SFTP
"""

from azure.cli.core import AzCommandsLoader

from azext_sftp._help import helps # pylint: disable=unused-import


class SftpCommandsLoader(AzCommandsLoader):
"""Command loader for the SFTP extension."""

def __init__(self, cli_ctx=None):
from azure.cli.core.commands import CliCommandType
from azext_sftp._client_factory import cf_sftp

sftp_custom = CliCommandType(
operations_tmpl='azext_sftp.custom#{}',
client_factory=cf_sftp)

super(SftpCommandsLoader, self).__init__(
cli_ctx=cli_ctx,
custom_command_type=sftp_custom)

def load_command_table(self, args):
"""Load the command table for SFTP commands."""
from azext_sftp.commands import load_command_table
load_command_table(self, args)
return self.command_table

def load_arguments(self, command):
"""Load arguments for SFTP commands."""
from azext_sftp._params import load_arguments
load_arguments(self, command)


COMMAND_LOADER_CLS = SftpCommandsLoader
12 changes: 12 additions & 0 deletions src/sftp/azext_sftp/_client_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

def cf_sftp(cli_ctx, *_):
"""
Client factory for SFTP extension.
This extension doesn't require a specific Azure management client
as it operates using SSH/SFTP protocols directly.
"""
return None
98 changes: 98 additions & 0 deletions src/sftp/azext_sftp/_help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# coding=utf-8
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from knack.help_files import helps # pylint: disable=unused-import


helps['sftp'] = """
type: group
short-summary: Commands to connect to Azure Storage Accounts via SFTP
long-summary: |
These commands allow you to generate certificates and connect to Azure Storage Accounts using SFTP.

PREREQUISITES:
- Azure Storage Account with SFTP enabled
- Appropriate RBAC permissions (Storage Blob Data Contributor or similar)
- Azure CLI authentication (az login)
- Network connectivity to Azure Storage endpoints

The SFTP extension provides two main capabilities:
1. Certificate generation using Azure AD authentication (similar to 'az ssh cert')
2. Fully managed SFTP connections to Azure Storage with automatic credential handling

AUTHENTICATION MODES:
- Fully managed: No credentials needed - automatically generates SSH certificate
- Certificate-based: Use existing SSH certificate file
- Key-based: Use SSH public/private key pair (generates certificate automatically)

This extension closely follows the patterns established by the SSH extension.
"""

helps['sftp cert'] = """
type: command
short-summary: Generate SSH certificate for SFTP authentication
long-summary: |
Generate an SSH certificate that can be used for authenticating to Azure Storage SFTP endpoints.
This uses Azure AD authentication to generate a certificate similar to 'az ssh cert'.

CERTIFICATE NAMING:
- Generated certificates have '-aadcert.pub' suffix (e.g., id_rsa-aadcert.pub)
- Certificates are valid for a limited time (typically 1 hour)
- Private keys are generated with 'id_rsa' name when key pair is created

The certificate can be used with 'az sftp connect' or with standard SFTP clients.
examples:
- name: Generate a certificate using an existing public key
text: az sftp cert --public-key-file ~/.ssh/id_rsa.pub --file ~/my_cert.pub
- name: Generate a certificate and create a new key pair in the same directory
text: az sftp cert --file ~/my_cert.pub
- name: Generate a certificate with custom SSH client folder
text: az sftp cert --file ~/my_cert.pub --ssh-client-folder "C:\\Program Files\\OpenSSH"
"""

helps['sftp connect'] = """
type: command
short-summary: Connect to Azure Storage Account via SFTP
long-summary: |
Establish an SFTP connection to an Azure Storage Account.

AUTHENTICATION MODES:
1. Fully managed (RECOMMENDED): Run without credentials - automatically generates SSH certificate
and establishes connection. Credentials are cleaned up after use.

2. Certificate-based: Use existing SSH certificate file. Certificate must be generated with
'az sftp cert' or compatible with Azure AD authentication.

3. Key-based: Provide SSH keys - command will generate certificate automatically from your keys.

CONNECTION DETAILS:
- Username format: {storage-account}.{azure-username}
- Port: Uses SSH default (typically 22) unless specified with --port
- Endpoints resolved automatically based on Azure cloud environment:
* Azure Public: {storage-account}.blob.core.windows.net
* Azure China: {storage-account}.blob.core.chinacloudapi.cn
* Azure Government: {storage-account}.blob.core.usgovcloudapi.net

SECURITY:
- Generated credentials are automatically cleaned up after connection
- Temporary files stored in secure temporary directories
- Certificate validity is checked and renewed if expired
examples:
- name: Connect with automatic certificate generation (fully managed - RECOMMENDED)
text: az sftp connect --storage-account mystorageaccount
- name: Connect to storage account with existing certificate
text: az sftp connect --storage-account mystorageaccount --certificate-file ~/my_cert.pub
- name: Connect with existing SSH key pair
text: az sftp connect --storage-account mystorageaccount --public-key-file ~/.ssh/id_rsa.pub --private-key-file ~/.ssh/id_rsa
- name: Connect with custom port
text: az sftp connect --storage-account mystorageaccount --port 2222
- name: Connect with additional SFTP arguments for debugging
text: az sftp connect --storage-account mystorageaccount --sftp-args "-v"
- name: Connect with custom SSH client folder (Windows)
text: az sftp connect --storage-account mystorageaccount --ssh-client-folder "C:\\Program Files\\OpenSSH"
- name: Run batch commands after connecting
text: az sftp connect --storage-account mystorageaccount --batch-commands "ls\\nget file.txt\\nbye"
"""
46 changes: 46 additions & 0 deletions src/sftp/azext_sftp/_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
# pylint: disable=line-too-long

from knack.arguments import CLIArgumentType


def load_arguments(self, _):

with self.argument_context('sftp cert') as c:
c.argument('cert_path', options_list=['--file', '-f'],
help='The file path to write the SSH cert to, defaults to public key path with -aadcert.pub appended')
c.argument('public_key_file', options_list=['--public-key-file', '-p'],
help='The RSA public key file path. If not provided, '
'generated key pair is stored in the same directory as --file.')
c.argument('ssh_client_folder', options_list=['--ssh-client-folder'],
help='Folder path that contains ssh executables (ssh-keygen, ssh). '
'Default to ssh executables in your PATH or C:\\Windows\\System32\\OpenSSH on Windows.')

with self.argument_context('sftp connect') as c:
c.argument('storage_account', options_list=['--storage-account', '-s'],
help='Azure Storage Account name for SFTP connection. Must have SFTP enabled.')
c.argument('port', options_list=['--port'],
help='SFTP port. If not specified, uses SSH default port (typically 22).',
type=int)
c.argument('cert_file', options_list=['--certificate-file', '-c'],
help='Path to SSH certificate file for authentication. '
'Must be generated with "az sftp cert" or compatible Azure AD certificate. '
'If not provided, certificate will be generated automatically.')
c.argument('private_key_file', options_list=['--private-key-file', '-i'],
help='Path to RSA private key file. If provided without certificate, '
'a certificate will be generated automatically from this key.')
c.argument('public_key_file', options_list=['--public-key-file', '-p'],
help='Path to RSA public key file. If provided without certificate, '
'a certificate will be generated automatically from this key.')
c.argument('sftp_args', options_list=['--sftp-args'],
help='Additional arguments to pass to the SFTP client. '
'Example: "-v" for verbose output, "-o ConnectTimeout=30" for custom timeout.')
c.argument('ssh_client_folder', options_list=['--ssh-client-folder'],
help='Path to folder containing SSH client executables (ssh, sftp, ssh-keygen). '
'Default: Uses executables from PATH or C:\\Windows\\System32\\OpenSSH on Windows.')
c.argument('sftp_batch_commands', options_list=['--batch-commands'],
help='SFTP batch commands to execute after connecting (non-interactive mode). '
'Separate commands with \\n. Example: "ls\\nget file.txt\\nbye"')
29 changes: 29 additions & 0 deletions src/sftp/azext_sftp/_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core import azclierror
from azure.cli.core.commands.client_factory import get_subscription_id
from msrestazure.tools import is_valid_resource_id, resource_id


def storage_account_name_or_id_validator(cmd, namespace):
"""
Validator for storage account name or resource ID.
Converts storage account name to full resource ID if needed.
"""
if namespace.storage_account:
if not is_valid_resource_id(namespace.storage_account):
if not hasattr(namespace, 'resource_group_name') or not namespace.resource_group_name:
raise azclierror.RequiredArgumentMissingError(
"When providing storage account name, --resource-group is required. "
"Alternatively, provide the full resource ID."
)
namespace.storage_account = resource_id(
subscription=get_subscription_id(cmd.cli_ctx),
resource_group=namespace.resource_group_name,
namespace='Microsoft.Storage',
type='storageAccounts',
name=namespace.storage_account
)
5 changes: 5 additions & 0 deletions src/sftp/azext_sftp/azext_metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"azext.isPreview": true,
"azext.minCliCoreVersion": "2.0.67",
"azext.maxCliCoreVersion": "2.99.0"
}
24 changes: 24 additions & 0 deletions src/sftp/azext_sftp/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""
Command definitions for the Azure CLI SFTP extension.

This module defines the available SFTP commands and their routing
to the appropriate custom functions.
"""


def load_command_table(self, _):
"""
Load command table for SFTP extension.

Commands:
- sftp cert: Generate SSH certificates for SFTP authentication
- sftp connect: Connect to Azure Storage Account via SFTP
"""
with self.command_group('sftp') as g:
g.custom_command('cert', 'sftp_cert')
g.custom_command('connect', 'sftp_connect')
29 changes: 29 additions & 0 deletions src/sftp/azext_sftp/connectivity_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import json
import base64

from knack import log

logger = log.get_logger(__name__)


def format_relay_info_string(relay_info):
relay_info_string = json.dumps(
{
"relay": {
"namespaceName": relay_info['namespaceName'],
"namespaceNameSuffix": relay_info['namespaceNameSuffix'],
"hybridConnectionName": relay_info['hybridConnectionName'],
"accessKey": relay_info['accessKey'],
"expiresOn": relay_info['expiresOn'],
"serviceConfigurationToken": relay_info['serviceConfigurationToken']
}
})
result_bytes = relay_info_string.encode("ascii")
enc = base64.b64encode(result_bytes)
base64_result_string = enc.decode("ascii")
return base64_result_string
39 changes: 39 additions & 0 deletions src/sftp/azext_sftp/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from colorama import Fore, Style

# File system constants
WINDOWS_INVALID_FOLDERNAME_CHARS = "\\/*:<>?\"|"

# Default SSH/SFTP configuration
DEFAULT_SSH_PORT = 22
DEFAULT_SFTP_PORT = 22
AZURE_STORAGE_SFTP_PORT = 22

# SSH/SFTP client configuration
SSH_CONNECT_TIMEOUT = 30
SSH_SERVER_ALIVE_INTERVAL = 60
SSH_SERVER_ALIVE_COUNT_MAX = 3

# Certificate and key file naming
SSH_PRIVATE_KEY_NAME = "id_rsa"
SSH_PUBLIC_KEY_NAME = "id_rsa.pub"
SSH_CERT_SUFFIX = "-aadcert.pub"

# Error messages and recommendations
RECOMMENDATION_SSH_CLIENT_NOT_FOUND = (
Fore.YELLOW +
"Ensure OpenSSH is installed correctly.\n"
"Alternatively, use --ssh-client-folder to provide OpenSSH folder path." +
Style.RESET_ALL
)

RECOMMENDATION_STORAGE_ACCOUNT_SFTP = (
Fore.YELLOW +
"Ensure your Azure Storage Account has SFTP enabled.\n"
"Verify your account permissions include Storage Blob Data Contributor or similar." +
Style.RESET_ALL
)
Loading
Loading