azure-deploy-cli 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- azure_deploy_cli/__init__.py +44 -0
- azure_deploy_cli/_version.py +34 -0
- azure_deploy_cli/aca/aca_cli.py +518 -0
- azure_deploy_cli/aca/bash/aca-cert/create.sh +203 -0
- azure_deploy_cli/aca/bash/aca-cert/destroy.sh +44 -0
- azure_deploy_cli/aca/deploy_aca.py +794 -0
- azure_deploy_cli/aca/model.py +35 -0
- azure_deploy_cli/cli.py +66 -0
- azure_deploy_cli/identity/__init__.py +36 -0
- azure_deploy_cli/identity/group.py +84 -0
- azure_deploy_cli/identity/identity_cli.py +453 -0
- azure_deploy_cli/identity/managed_identity.py +177 -0
- azure_deploy_cli/identity/models.py +167 -0
- azure_deploy_cli/identity/py.typed +0 -0
- azure_deploy_cli/identity/role.py +338 -0
- azure_deploy_cli/identity/service_principal.py +268 -0
- azure_deploy_cli/py.typed +0 -0
- azure_deploy_cli/utils/__init__.py +0 -0
- azure_deploy_cli/utils/azure_cli.py +96 -0
- azure_deploy_cli/utils/docker.py +137 -0
- azure_deploy_cli/utils/env.py +108 -0
- azure_deploy_cli/utils/key_vault.py +11 -0
- azure_deploy_cli/utils/logging.py +125 -0
- azure_deploy_cli/utils/py.typed +0 -0
- azure_deploy_cli-0.1.6.dist-info/METADATA +678 -0
- azure_deploy_cli-0.1.6.dist-info/RECORD +30 -0
- azure_deploy_cli-0.1.6.dist-info/WHEEL +5 -0
- azure_deploy_cli-0.1.6.dist-info/entry_points.txt +3 -0
- azure_deploy_cli-0.1.6.dist-info/licenses/LICENSE +373 -0
- azure_deploy_cli-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from azure.mgmt.keyvault import KeyVaultManagementClient
|
|
4
|
+
|
|
5
|
+
from ..identity.models import ManagedIdentity
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SecretKeyVaultConfig:
|
|
10
|
+
key_vault_client: KeyVaultManagementClient
|
|
11
|
+
key_vault_name: str
|
|
12
|
+
secret_names: list[str]
|
|
13
|
+
user_identity: ManagedIdentity
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RevisionDeploymentResult:
|
|
18
|
+
"""Result of a revision deployment operation."""
|
|
19
|
+
|
|
20
|
+
revision_name: str
|
|
21
|
+
active: bool
|
|
22
|
+
health_state: str
|
|
23
|
+
provisioning_state: str
|
|
24
|
+
running_state: str
|
|
25
|
+
revision_url: str | None
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def is_healthy(self) -> bool:
|
|
29
|
+
"""Check if the revision is healthy and active."""
|
|
30
|
+
return (
|
|
31
|
+
self.active
|
|
32
|
+
and self.health_state == "Healthy"
|
|
33
|
+
and self.provisioning_state == "Provisioned"
|
|
34
|
+
and self.running_state not in ("Stopped", "Degraded", "Failed")
|
|
35
|
+
)
|
azure_deploy_cli/cli.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .aca import aca_cli
|
|
5
|
+
from .identity import identity_cli
|
|
6
|
+
from .utils.logging import configure_logging, get_logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
"""
|
|
11
|
+
Main CLI entry point
|
|
12
|
+
|
|
13
|
+
Routes to different tool namespaces:
|
|
14
|
+
- azid: Azure identity management (service principals, credentials, RBAC)
|
|
15
|
+
- azaca: Azure Container Apps management (identity and role setup)
|
|
16
|
+
"""
|
|
17
|
+
parser = argparse.ArgumentParser(
|
|
18
|
+
description="Azure Deploy CLI",
|
|
19
|
+
prog="azd",
|
|
20
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
21
|
+
epilog="""
|
|
22
|
+
Namespaces:
|
|
23
|
+
azid Azure identity management (service principals, credentials, roles)
|
|
24
|
+
azaca Azure Container Apps management (identity and role setup)
|
|
25
|
+
""",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--log-level",
|
|
30
|
+
type=str,
|
|
31
|
+
choices=["debug", "info", "warning", "error", "critical", "notset"],
|
|
32
|
+
default="info",
|
|
33
|
+
help="Set the logging level.",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
subparsers = parser.add_subparsers(dest="namespace", help="Tool namespace")
|
|
37
|
+
|
|
38
|
+
# Add identity namespace
|
|
39
|
+
identity_cli.add_commands(subparsers)
|
|
40
|
+
|
|
41
|
+
# Add ACA namespace
|
|
42
|
+
aca_cli.add_commands(subparsers)
|
|
43
|
+
|
|
44
|
+
args = parser.parse_args()
|
|
45
|
+
|
|
46
|
+
configure_logging(level=args.log_level)
|
|
47
|
+
logger = get_logger(__name__)
|
|
48
|
+
|
|
49
|
+
if not args.namespace:
|
|
50
|
+
parser.print_help()
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
if not hasattr(args, "func"):
|
|
54
|
+
logger.error("No command specified")
|
|
55
|
+
parser.print_help()
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
args.func(args)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.error(f"Fatal error: {str(e)}")
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import azure_deploy_cli.identity.identity_cli
|
|
2
|
+
from azure_deploy_cli.identity.managed_identity import (
|
|
3
|
+
create_or_get_user_identity,
|
|
4
|
+
delete_user_identity,
|
|
5
|
+
get_identity_principal_id,
|
|
6
|
+
)
|
|
7
|
+
from azure_deploy_cli.identity.models import (
|
|
8
|
+
AzureGroup,
|
|
9
|
+
ManagedIdentity,
|
|
10
|
+
RoleConfig,
|
|
11
|
+
RoleDefinition,
|
|
12
|
+
SPAuthCredentials,
|
|
13
|
+
SPAuthCredentialsWithSecret,
|
|
14
|
+
SPCreateResult,
|
|
15
|
+
)
|
|
16
|
+
from azure_deploy_cli.identity.role import assign_roles
|
|
17
|
+
from azure_deploy_cli.identity.service_principal import create_sp, reset_sp_credentials
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"SPAuthCredentials",
|
|
23
|
+
"SPAuthCredentialsWithSecret",
|
|
24
|
+
"RoleConfig",
|
|
25
|
+
"RoleDefinition",
|
|
26
|
+
"SPCreateResult",
|
|
27
|
+
"ManagedIdentity",
|
|
28
|
+
"AzureGroup",
|
|
29
|
+
"assign_roles",
|
|
30
|
+
"create_sp",
|
|
31
|
+
"create_or_get_user_identity",
|
|
32
|
+
"delete_user_identity",
|
|
33
|
+
"get_identity_principal_id",
|
|
34
|
+
"identity_cli",
|
|
35
|
+
"reset_sp_credentials",
|
|
36
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Azure AD Security Group lifecycle and role management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..utils.azure_cli import run_command
|
|
8
|
+
from ..utils.logging import get_logger
|
|
9
|
+
from .models import AzureGroup
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def list_cmd(group_name: str) -> list[str]:
|
|
15
|
+
"""Build command to list security groups by name."""
|
|
16
|
+
return [
|
|
17
|
+
"az",
|
|
18
|
+
"ad",
|
|
19
|
+
"group",
|
|
20
|
+
"list",
|
|
21
|
+
"--display-name",
|
|
22
|
+
group_name,
|
|
23
|
+
"--output",
|
|
24
|
+
"json",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def exists_group(group_name: str) -> str | None:
|
|
29
|
+
"""
|
|
30
|
+
Check if security group exists by name.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
group_name: Display name of the security group
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Object ID if found, None otherwise
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: If multiple groups with same name found
|
|
40
|
+
"""
|
|
41
|
+
result: list[dict[str, Any]] = run_command(list_cmd(group_name))
|
|
42
|
+
|
|
43
|
+
if not result or len(result) == 0:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
if len(result) > 1:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Multiple security groups found with name '{group_name}'. "
|
|
49
|
+
"Please use a more specific name."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
group_object_id: str = result[0]["id"]
|
|
53
|
+
return group_object_id
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_group(group_name: str) -> AzureGroup | None:
|
|
57
|
+
"""
|
|
58
|
+
Get existing security group by name.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
group_name: Display name of the security group
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
GroupAssignResult if found, None otherwise
|
|
65
|
+
"""
|
|
66
|
+
list_cmd_args: list[str] = list_cmd(group_name)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
existing_groups: list[dict[str, Any]] = run_command(list_cmd_args)
|
|
70
|
+
if existing_groups:
|
|
71
|
+
logger.info(f"Found security group '{group_name}'")
|
|
72
|
+
group = existing_groups[0]
|
|
73
|
+
object_id: str = group.get("id", "")
|
|
74
|
+
|
|
75
|
+
if not object_id:
|
|
76
|
+
raise ValueError("Failed to load existing group details")
|
|
77
|
+
|
|
78
|
+
return AzureGroup(
|
|
79
|
+
objectId=object_id,
|
|
80
|
+
displayName=group_name,
|
|
81
|
+
)
|
|
82
|
+
except (subprocess.CalledProcessError, json.JSONDecodeError):
|
|
83
|
+
return None
|
|
84
|
+
return None
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..utils.azure_cli import run_command
|
|
9
|
+
from ..utils.env import add_var_to_env_file, load_env_vars_from_files
|
|
10
|
+
from ..utils.logging import get_logger
|
|
11
|
+
from .group import get_group
|
|
12
|
+
from .managed_identity import create_or_get_user_identity
|
|
13
|
+
from .models import SPAuthCredentialsWithSecret
|
|
14
|
+
from .role import assign_role_by_files
|
|
15
|
+
from .service_principal import (
|
|
16
|
+
create_sp,
|
|
17
|
+
delete_service_principal_by_name,
|
|
18
|
+
reset_sp_credentials,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_credentials(env_file: Path | None, cred_key: str) -> Any:
|
|
25
|
+
"""
|
|
26
|
+
Load AZ_CREDENTIALS from env file or environment variable.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
env_file: Optional path to env file
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Dictionary with credentials (clientId, clientSecret, subscriptionId,
|
|
33
|
+
tenantId)
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
FileNotFoundError: If env file not found
|
|
37
|
+
ValueError: If credentials not found or invalid
|
|
38
|
+
json.JSONDecodeError: If AZ_CREDENTIALS JSON is invalid
|
|
39
|
+
"""
|
|
40
|
+
credentials_json: str = ""
|
|
41
|
+
|
|
42
|
+
if env_file:
|
|
43
|
+
env_vars = load_env_vars_from_files([env_file])
|
|
44
|
+
c = env_vars.get(cred_key, "")
|
|
45
|
+
if c:
|
|
46
|
+
credentials_json = c
|
|
47
|
+
|
|
48
|
+
if not credentials_json:
|
|
49
|
+
credentials_json = os.environ.get(cred_key, "")
|
|
50
|
+
|
|
51
|
+
if not credentials_json:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Cannot find {cred_key} from --env-file or {cred_key} environment variable"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
creds_dict = json.loads(credentials_json)
|
|
57
|
+
return creds_dict
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _save_credentials(
|
|
61
|
+
credentials: SPAuthCredentialsWithSecret, env_file: Path | None, cred_key: str
|
|
62
|
+
) -> None:
|
|
63
|
+
logger.critical(f"Saving credentials to env file: {env_file}")
|
|
64
|
+
|
|
65
|
+
if not env_file:
|
|
66
|
+
logger.warning("No --env-file provided; credentials will not be saved to file")
|
|
67
|
+
|
|
68
|
+
if env_file:
|
|
69
|
+
if isinstance(credentials, SPAuthCredentialsWithSecret):
|
|
70
|
+
try:
|
|
71
|
+
logger.info(f"Updating credentials in '{env_file}'")
|
|
72
|
+
credentials_json = json.dumps(credentials.to_dict(), separators=(",", ":"))
|
|
73
|
+
add_var_to_env_file({cred_key: credentials_json}, env_file)
|
|
74
|
+
logger.success(f"Credentials saved to '{env_file}'")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
raise Exception(f"Failed to save credentials: {str(e)}") from None
|
|
77
|
+
else:
|
|
78
|
+
logger.warning(
|
|
79
|
+
f"Credentials do not contain secret; cannot save to "
|
|
80
|
+
f"{env_file}. Only credentials with secrets can be "
|
|
81
|
+
f"persisted."
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
logger.warning("No --env-file provided; credentials will not be saved to file")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def cli_create_and_assign(args: Any) -> None:
|
|
88
|
+
try:
|
|
89
|
+
sp_result = create_sp(args.sp_name, skip_assignment=True)
|
|
90
|
+
|
|
91
|
+
if args.reset_secrets and not isinstance(
|
|
92
|
+
sp_result.authCredentials, SPAuthCredentialsWithSecret
|
|
93
|
+
):
|
|
94
|
+
try:
|
|
95
|
+
credentials = reset_sp_credentials(args.sp_name)
|
|
96
|
+
sp_result.authCredentials = credentials
|
|
97
|
+
except Exception as e:
|
|
98
|
+
raise Exception(f"Failed to reset secrets: {str(e)}") from None
|
|
99
|
+
|
|
100
|
+
_save_credentials(
|
|
101
|
+
sp_result.authCredentials,
|
|
102
|
+
args.env_file,
|
|
103
|
+
args.cred_key,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if args.print:
|
|
107
|
+
logger.critical(
|
|
108
|
+
f"Created credentials: {json.dumps(sp_result.authCredentials.to_dict(), indent=2)}",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
assign_role_by_files(
|
|
112
|
+
sp_result.objectId,
|
|
113
|
+
args.roles_config,
|
|
114
|
+
args.env_vars_files,
|
|
115
|
+
)
|
|
116
|
+
logger.success("Service principal created and roles assigned successfully")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.error(str(e))
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def cli_reset_credentials(args: Any) -> None:
|
|
123
|
+
try:
|
|
124
|
+
credentials = reset_sp_credentials(args.sp_name)
|
|
125
|
+
|
|
126
|
+
_save_credentials(credentials, args.env_file, args.cred_key)
|
|
127
|
+
|
|
128
|
+
if args.print:
|
|
129
|
+
logger.info(
|
|
130
|
+
f"Created credentials: {json.dumps(credentials.to_dict(), indent=2)}",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
logger.success("Credentials reset successfully")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(str(e))
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def cli_login(args: Any) -> None:
|
|
140
|
+
try:
|
|
141
|
+
credentials_dict = _load_credentials(args.env_file, args.cred_key)
|
|
142
|
+
credentials = SPAuthCredentialsWithSecret(**credentials_dict)
|
|
143
|
+
|
|
144
|
+
logger.info(f"Logging in with service principal {credentials.clientId}")
|
|
145
|
+
|
|
146
|
+
login_cmd: list[str] = [
|
|
147
|
+
"az",
|
|
148
|
+
"login",
|
|
149
|
+
"--service-principal",
|
|
150
|
+
"-u",
|
|
151
|
+
credentials.clientId,
|
|
152
|
+
"-p",
|
|
153
|
+
credentials.clientSecret,
|
|
154
|
+
"--tenant",
|
|
155
|
+
credentials.tenantId,
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
run_command(login_cmd)
|
|
159
|
+
logger.success(f"Successfully logged in with service principal {credentials.clientId}")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(str(e))
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def cli_delete_service_principal(args: Any) -> None:
|
|
166
|
+
try:
|
|
167
|
+
delete_service_principal_by_name(args.sp_name)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(str(e))
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def cli_assign_roles_to_group(args: Any) -> None:
|
|
174
|
+
try:
|
|
175
|
+
group_result = get_group(args.group_name)
|
|
176
|
+
|
|
177
|
+
if group_result is None:
|
|
178
|
+
raise Exception(f"Security group '{args.group_name}' not found")
|
|
179
|
+
|
|
180
|
+
logger.success(
|
|
181
|
+
f"Found security group '{args.group_name}' with object ID: {group_result.objectId}"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
assign_role_by_files(
|
|
185
|
+
group_result.objectId,
|
|
186
|
+
args.roles_config,
|
|
187
|
+
args.env_vars_files,
|
|
188
|
+
object_type="Group",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
logger.success("Roles assigned to security group successfully")
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.error(str(e))
|
|
194
|
+
sys.exit(1)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def cli_create_and_assign_managed_identity(args: Any) -> None:
|
|
198
|
+
try:
|
|
199
|
+
logger.info(f"Setting up managed identity '{args.identity_name}'...")
|
|
200
|
+
identity_result = create_or_get_user_identity(
|
|
201
|
+
args.identity_name,
|
|
202
|
+
args.resource_group,
|
|
203
|
+
args.location,
|
|
204
|
+
)
|
|
205
|
+
logger.success(f"Managed identity ready: {identity_result.resourceId}")
|
|
206
|
+
|
|
207
|
+
if args.roles_config:
|
|
208
|
+
assign_role_by_files(
|
|
209
|
+
identity_result.principalId,
|
|
210
|
+
args.roles_config,
|
|
211
|
+
args.env_vars_files,
|
|
212
|
+
)
|
|
213
|
+
logger.success("Roles assigned to managed identity successfully")
|
|
214
|
+
else:
|
|
215
|
+
logger.info("No role config provided; skipping role assignment")
|
|
216
|
+
|
|
217
|
+
logger.success(
|
|
218
|
+
f"Managed identity '{args.identity_name}' created and ready for use. "
|
|
219
|
+
f"Resource ID: {identity_result.resourceId}"
|
|
220
|
+
)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.error(str(e))
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def add_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
227
|
+
azid_parser = subparsers.add_parser(
|
|
228
|
+
"azid",
|
|
229
|
+
help="Azure identity management (service principals, credentials, roles)",
|
|
230
|
+
description="Manage Azure service principals, credentials, and role assignments",
|
|
231
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
232
|
+
epilog="""
|
|
233
|
+
Examples:
|
|
234
|
+
# Create a new service principal and assign roles
|
|
235
|
+
cc azid create-sp-and-assign-roles \\
|
|
236
|
+
--sp-name my-sp \\
|
|
237
|
+
--roles-config roles-config.json \\
|
|
238
|
+
--env-vars-files .env.local \\
|
|
239
|
+
--env-file .env.credentials \\
|
|
240
|
+
--print
|
|
241
|
+
|
|
242
|
+
# Reset credentials for an existing service principal
|
|
243
|
+
cc azid reset-sp-credentials \\
|
|
244
|
+
--sp-name my-sp \\
|
|
245
|
+
--env-file .env.credentials \\
|
|
246
|
+
--print
|
|
247
|
+
|
|
248
|
+
# Assign roles to a security group
|
|
249
|
+
cc azid assign-roles-to-group \\
|
|
250
|
+
--group-name "My Security Team" \\
|
|
251
|
+
--roles-config roles-config.json \\
|
|
252
|
+
--env-vars-files .env.local
|
|
253
|
+
|
|
254
|
+
# Create user-assigned managed identity and assign roles
|
|
255
|
+
cc azid create-and-assign-managed-identity \\
|
|
256
|
+
--identity-name my-app-identity \\
|
|
257
|
+
--resource-group my-rg \\
|
|
258
|
+
--location eastus \\
|
|
259
|
+
--roles-config roles-config.json \\
|
|
260
|
+
--env-vars-files .env.local
|
|
261
|
+
|
|
262
|
+
# Login using stored credentials
|
|
263
|
+
cc azid login --env-file .env.credentials
|
|
264
|
+
""",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
azid_subparsers = azid_parser.add_subparsers(
|
|
268
|
+
dest="azid_command", help="Identity command to execute"
|
|
269
|
+
)
|
|
270
|
+
azid_subparsers.required = True
|
|
271
|
+
|
|
272
|
+
create_parser = azid_subparsers.add_parser(
|
|
273
|
+
"create-sp-and-assign-roles",
|
|
274
|
+
help="Create a service principal and assign roles",
|
|
275
|
+
description=(
|
|
276
|
+
"Create a new service principal in Azure and assign it roles "
|
|
277
|
+
"based on a configuration file."
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
create_parser.add_argument(
|
|
281
|
+
"--sp-name",
|
|
282
|
+
required=True,
|
|
283
|
+
help="Name of the service principal to create",
|
|
284
|
+
)
|
|
285
|
+
create_parser.add_argument(
|
|
286
|
+
"--roles-config",
|
|
287
|
+
required=True,
|
|
288
|
+
type=Path,
|
|
289
|
+
help="Path to roles-config.json file containing role definitions",
|
|
290
|
+
)
|
|
291
|
+
create_parser.add_argument(
|
|
292
|
+
"--env-vars-files",
|
|
293
|
+
required=True,
|
|
294
|
+
type=Path,
|
|
295
|
+
nargs="+",
|
|
296
|
+
help="Paths to .env files with environment variables (can specify multiple files)",
|
|
297
|
+
)
|
|
298
|
+
create_parser.add_argument(
|
|
299
|
+
"-f",
|
|
300
|
+
"--env-file",
|
|
301
|
+
type=Path,
|
|
302
|
+
help="Path to environment file to save credentials (optional)",
|
|
303
|
+
)
|
|
304
|
+
create_parser.add_argument(
|
|
305
|
+
"-k",
|
|
306
|
+
"--cred-key",
|
|
307
|
+
type=str,
|
|
308
|
+
default="AZ_CREDENTIALS",
|
|
309
|
+
help="Credential key used to save credentials (optional)",
|
|
310
|
+
)
|
|
311
|
+
create_parser.add_argument(
|
|
312
|
+
"--print",
|
|
313
|
+
action="store_true",
|
|
314
|
+
help="Print credentials to stdout in JSON format",
|
|
315
|
+
)
|
|
316
|
+
create_parser.add_argument(
|
|
317
|
+
"--reset-secrets",
|
|
318
|
+
action="store_true",
|
|
319
|
+
help="Reset secrets after creation if SP already exists (has no secret)",
|
|
320
|
+
)
|
|
321
|
+
create_parser.set_defaults(func=cli_create_and_assign)
|
|
322
|
+
|
|
323
|
+
reset_parser = azid_subparsers.add_parser(
|
|
324
|
+
"reset-sp-credentials",
|
|
325
|
+
help="Reset service principal credentials",
|
|
326
|
+
description="Reset (rotate) credentials for an existing service principal.",
|
|
327
|
+
)
|
|
328
|
+
reset_parser.add_argument(
|
|
329
|
+
"--sp-name",
|
|
330
|
+
required=True,
|
|
331
|
+
help="Display name of the service principal",
|
|
332
|
+
)
|
|
333
|
+
reset_parser.add_argument(
|
|
334
|
+
"-f",
|
|
335
|
+
"--env-file",
|
|
336
|
+
type=Path,
|
|
337
|
+
help="Path to environment file to save credentials (optional)",
|
|
338
|
+
)
|
|
339
|
+
reset_parser.add_argument(
|
|
340
|
+
"-k",
|
|
341
|
+
"--cred-key",
|
|
342
|
+
type=str,
|
|
343
|
+
default="AZ_CREDENTIALS",
|
|
344
|
+
help="Credential key used to save credentials (optional)",
|
|
345
|
+
)
|
|
346
|
+
reset_parser.add_argument(
|
|
347
|
+
"--print",
|
|
348
|
+
action="store_true",
|
|
349
|
+
help="Print credentials to stdout in JSON format",
|
|
350
|
+
)
|
|
351
|
+
reset_parser.set_defaults(func=cli_reset_credentials)
|
|
352
|
+
|
|
353
|
+
login_parser = azid_subparsers.add_parser(
|
|
354
|
+
"login",
|
|
355
|
+
help="Login using service principal credentials",
|
|
356
|
+
description="Authenticate with Azure using stored service principal credentials.",
|
|
357
|
+
)
|
|
358
|
+
login_parser.add_argument(
|
|
359
|
+
"-f",
|
|
360
|
+
"--env-file",
|
|
361
|
+
type=Path,
|
|
362
|
+
help=(
|
|
363
|
+
"Path to environment file containing AZ_CREDENTIALS "
|
|
364
|
+
"(optional, checks env vars if not provided)"
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
login_parser.add_argument(
|
|
368
|
+
"-k",
|
|
369
|
+
"--cred-key",
|
|
370
|
+
type=str,
|
|
371
|
+
default="AZ_CREDENTIALS",
|
|
372
|
+
help="Credential key used to load credentials (optional)",
|
|
373
|
+
)
|
|
374
|
+
login_parser.set_defaults(func=cli_login)
|
|
375
|
+
|
|
376
|
+
delete_parser = azid_subparsers.add_parser(
|
|
377
|
+
"delete-sp",
|
|
378
|
+
help="Delete a service principal by name",
|
|
379
|
+
description="Delete a service principal from Azure by its display name.",
|
|
380
|
+
)
|
|
381
|
+
delete_parser.add_argument(
|
|
382
|
+
"--sp-name",
|
|
383
|
+
required=True,
|
|
384
|
+
help="Display name of the service principal to delete",
|
|
385
|
+
)
|
|
386
|
+
delete_parser.set_defaults(func=cli_delete_service_principal)
|
|
387
|
+
|
|
388
|
+
group_parser = azid_subparsers.add_parser(
|
|
389
|
+
"assign-roles-to-group",
|
|
390
|
+
help="Assign roles to a security group",
|
|
391
|
+
description=(
|
|
392
|
+
"Assign roles to an Azure AD security group based on a configuration file. "
|
|
393
|
+
"This is similar to create-sp-and-assign-roles but for security groups "
|
|
394
|
+
"instead of service principals."
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
group_parser.add_argument(
|
|
398
|
+
"--group-name",
|
|
399
|
+
required=True,
|
|
400
|
+
help="Display name of the Azure AD security group",
|
|
401
|
+
)
|
|
402
|
+
group_parser.add_argument(
|
|
403
|
+
"--roles-config",
|
|
404
|
+
required=True,
|
|
405
|
+
type=Path,
|
|
406
|
+
help="Path to roles-config.json file containing role definitions",
|
|
407
|
+
)
|
|
408
|
+
group_parser.add_argument(
|
|
409
|
+
"--env-vars-files",
|
|
410
|
+
required=True,
|
|
411
|
+
type=Path,
|
|
412
|
+
nargs="+",
|
|
413
|
+
help="Paths to .env files with environment variables (can specify multiple files)",
|
|
414
|
+
)
|
|
415
|
+
group_parser.set_defaults(func=cli_assign_roles_to_group)
|
|
416
|
+
|
|
417
|
+
mi_parser = azid_subparsers.add_parser(
|
|
418
|
+
"create-and-assign-managed-identity",
|
|
419
|
+
help="Create a user-assigned managed identity and assign roles",
|
|
420
|
+
description=(
|
|
421
|
+
"Create a new user-assigned managed identity in Azure and optionally "
|
|
422
|
+
"assign it roles based on a configuration file."
|
|
423
|
+
),
|
|
424
|
+
)
|
|
425
|
+
mi_parser.add_argument(
|
|
426
|
+
"--identity-name",
|
|
427
|
+
required=True,
|
|
428
|
+
help="Name of the user-assigned managed identity to create or retrieve",
|
|
429
|
+
)
|
|
430
|
+
mi_parser.add_argument(
|
|
431
|
+
"--resource-group",
|
|
432
|
+
required=True,
|
|
433
|
+
help="Azure resource group name where the identity will be created",
|
|
434
|
+
)
|
|
435
|
+
mi_parser.add_argument(
|
|
436
|
+
"--location",
|
|
437
|
+
required=True,
|
|
438
|
+
help="Azure region location for the identity (e.g., eastus, westus2)",
|
|
439
|
+
)
|
|
440
|
+
mi_parser.add_argument(
|
|
441
|
+
"--roles-config",
|
|
442
|
+
required=False,
|
|
443
|
+
type=Path,
|
|
444
|
+
help="Path to roles-config.json file containing role definitions (optional)",
|
|
445
|
+
)
|
|
446
|
+
mi_parser.add_argument(
|
|
447
|
+
"--env-vars-files",
|
|
448
|
+
required=False,
|
|
449
|
+
type=Path,
|
|
450
|
+
nargs="+",
|
|
451
|
+
help="Paths to .env files with environment variables (can specify multiple files)",
|
|
452
|
+
)
|
|
453
|
+
mi_parser.set_defaults(func=cli_create_and_assign_managed_identity)
|