Skip to content

Commit 250d6db

Browse files
authored
[Profile] az login: Add --certificate for authenticating with service principal certificate (#30091)
1 parent 483ecc8 commit 250d6db

5 files changed

Lines changed: 40 additions & 14 deletions

File tree

src/azure-cli-core/azure/cli/core/auth/identity.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
"Select the account you want to log in with. "
3838
"For more information on login with Azure CLI, see https://go.microsoft.com/fwlink/?linkid=2271136")
3939

40+
PASSWORD_CERTIFICATE_WARNING = (
41+
"Passing the service principal certificate with `--password` is deprecated and will be removed "
42+
"by version 2.74. Please use `--certificate` instead.")
43+
4044
logger = get_logger(__name__)
4145

4246

@@ -303,7 +307,9 @@ def build_from_credential(cls, tenant_id, client_id, credential):
303307
return ServicePrincipalAuth(entry)
304308

305309
@classmethod
306-
def build_credential(cls, secret_or_certificate=None, client_assertion=None, use_cert_sn_issuer=None):
310+
def build_credential(cls, secret_or_certificate=None,
311+
certificate=None, use_cert_sn_issuer=None,
312+
client_assertion=None):
307313
"""Build credential from user input. The credential looks like below, but only one key can exist.
308314
{
309315
'client_secret': 'my_secret',
@@ -312,9 +318,15 @@ def build_credential(cls, secret_or_certificate=None, client_assertion=None, use
312318
}
313319
"""
314320
entry = {}
315-
if secret_or_certificate:
321+
if certificate:
322+
entry[_CERTIFICATE] = os.path.expanduser(certificate)
323+
if use_cert_sn_issuer:
324+
entry[_USE_CERT_SN_ISSUER] = use_cert_sn_issuer
325+
elif secret_or_certificate:
326+
# TODO: Make secret_or_certificate secret only
316327
user_expanded = os.path.expanduser(secret_or_certificate)
317328
if os.path.isfile(user_expanded):
329+
logger.warning(PASSWORD_CERTIFICATE_WARNING)
318330
entry[_CERTIFICATE] = user_expanded
319331
if use_cert_sn_issuer:
320332
entry[_USE_CERT_SN_ISSUER] = use_cert_sn_issuer

src/azure-cli-core/azure/cli/core/auth/tests/test_identity.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,29 +265,36 @@ def test_service_principal_auth_client_assertion(self):
265265

266266
def test_build_credential(self):
267267
# secret
268-
cred = ServicePrincipalAuth.build_credential("test_secret")
268+
cred = ServicePrincipalAuth.build_credential(secret_or_certificate="test_secret")
269269
assert cred == {"client_secret": "test_secret"}
270270

271271
# secret with '~', which is preserved as-is
272-
cred = ServicePrincipalAuth.build_credential("~test_secret")
272+
cred = ServicePrincipalAuth.build_credential(secret_or_certificate="~test_secret")
273273
assert cred == {"client_secret": "~test_secret"}
274274

275+
# certificate as password (deprecated)
276+
current_dir = os.path.dirname(os.path.realpath(__file__))
277+
test_cert_file = os.path.join(current_dir, 'sp_cert.pem')
278+
cred = ServicePrincipalAuth.build_credential(secret_or_certificate=test_cert_file)
279+
assert cred == {'certificate': test_cert_file}
280+
275281
# certificate
276282
current_dir = os.path.dirname(os.path.realpath(__file__))
277283
test_cert_file = os.path.join(current_dir, 'sp_cert.pem')
278-
cred = ServicePrincipalAuth.build_credential(test_cert_file)
284+
cred = ServicePrincipalAuth.build_credential(certificate=test_cert_file)
279285
assert cred == {'certificate': test_cert_file}
280286

281287
# certificate path with '~', which expands to HOME folder
282288
import shutil
283289
home = os.path.expanduser('~')
284290
home_cert = os.path.join(home, 'sp_cert.pem') # C:\Users\username\sp_cert.pem
285291
shutil.copyfile(test_cert_file, home_cert)
286-
cred = ServicePrincipalAuth.build_credential(os.path.join('~', 'sp_cert.pem')) # ~\sp_cert.pem
292+
cred = ServicePrincipalAuth.build_credential(certificate=os.path.join('~', 'sp_cert.pem')) # ~\sp_cert.pem
287293
assert cred == {'certificate': home_cert}
288294
os.remove(home_cert)
289295

290-
cred = ServicePrincipalAuth.build_credential(test_cert_file, use_cert_sn_issuer=True)
296+
# Certificate with use_cert_sn_issuer=True
297+
cred = ServicePrincipalAuth.build_credential(certificate=test_cert_file, use_cert_sn_issuer=True)
291298
assert cred == {'certificate': test_cert_file, 'use_cert_sn_issuer': True}
292299

293300
# client assertion

src/azure-cli/azure/cli/command_modules/profile/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ def load_arguments(self, command):
4747
c.argument('username', options_list=['--username', '-u'],
4848
help='User name, service principal client ID, or managed identity ID.')
4949
c.argument('password', options_list=['--password', '-p'],
50-
help='Provide credentials such as a user password, a service principal secret or a PEM file '
51-
'with key and public certificate. Will prompt if not given.')
50+
help='User password or service principal secret. Will prompt if not given.')
5251
c.argument('tenant', options_list=['--tenant', '-t'], validator=validate_tenant,
5352
help='The Microsoft Entra tenant, must be provided when using a service principal.')
5453
c.argument('scopes', options_list=['--scope'], nargs='+',
@@ -66,6 +65,7 @@ def load_arguments(self, command):
6665
# Service principal
6766
c.argument('service_principal', action='store_true',
6867
help='Log in with a service principal.')
68+
c.argument('certificate', help='PEM file with key and public certificate.')
6969
c.argument('use_cert_sn_issuer', action='store_true',
7070
help='Use Subject Name + Issuer (SN+I) authentication in order to support automatic '
7171
'certificate rolls.')

src/azure-cli/azure/cli/command_modules/profile/_help.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
For more details, see https://go.microsoft.com/fwlink/?linkid=2276314
2222
2323
24+
[WARNING] Passing the service principal certificate with `--password` is deprecated and will be removed
25+
by version 2.74. Please use `--certificate` instead.
26+
27+
2428
To log in with a service principal, specify --service-principal.
2529
2630
@@ -35,8 +39,8 @@
3539
text: az login -u johndoe@contoso.com -p VerySecret
3640
- name: Log in with a service principal using client secret. Use -p=secret if the first character of the password is '-'.
3741
text: az login --service-principal -u http://azure-cli-2016-08-05-14-31-15 -p VerySecret --tenant contoso.onmicrosoft.com
38-
- name: Log in with a service principal using client certificate.
39-
text: az login --service-principal -u http://azure-cli-2016-08-05-14-31-15 -p ~/mycertfile.pem --tenant contoso.onmicrosoft.com
42+
- name: Log in with a service principal using certificate.
43+
text: az login --service-principal -u http://azure-cli-2016-08-05-14-31-15 --certificate ~/mycertfile.pem --tenant contoso.onmicrosoft.com
4044
- name: Log in with a system-assigned managed identity.
4145
text: az login --identity
4246
- name: Log in with a user-assigned managed identity. You must specify the client ID, object ID or resource ID of the user-assigned managed identity with --username.

src/azure-cli/azure/cli/command_modules/profile/custom.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_
119119
# Device code flow
120120
use_device_code=False,
121121
# Service principal
122-
service_principal=None, use_cert_sn_issuer=None, client_assertion=None,
122+
service_principal=None, certificate=None, use_cert_sn_issuer=None, client_assertion=None,
123123
# Managed identity
124124
identity=False):
125125
"""Log in to access Azure subscriptions"""
@@ -148,7 +148,7 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_
148148
logger.warning(_CLOUD_CONSOLE_LOGIN_WARNING)
149149

150150
if username:
151-
if not (password or client_assertion):
151+
if not (password or client_assertion or certificate):
152152
try:
153153
password = prompt_pass('Password: ')
154154
except NoTTYException:
@@ -158,7 +158,10 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_
158158

159159
if service_principal:
160160
from azure.cli.core.auth.identity import ServicePrincipalAuth
161-
password = ServicePrincipalAuth.build_credential(password, client_assertion, use_cert_sn_issuer)
161+
password = ServicePrincipalAuth.build_credential(
162+
secret_or_certificate=password,
163+
certificate=certificate, use_cert_sn_issuer=use_cert_sn_issuer,
164+
client_assertion=client_assertion)
162165

163166
login_experience_v2 = cmd.cli_ctx.config.getboolean('core', 'login_experience_v2', fallback=True)
164167
# Send login_experience_v2 config to telemetry

0 commit comments

Comments
 (0)