Azure / azure-cli

Azure Command-Line Interface
MIT License
3.97k stars 2.95k forks source link

2.28 breaks `az iot dps certificate generate-verification-code` #20002

Open kensykora opened 2 years ago

kensykora commented 2 years ago

Describe the bug

As of 2.28 (does not occur in 2.27 or earlier) the command to generate a verification code for a DPS certificate fails with this error.

Command Name az iot dps certificate generate-verification-code

Errors:

ksykora@DESKTOP-LSEMUSR:~/ops-infrastructure-azure/v2/iothub$ ETAG=$(az iot dps certificate show -o json --query etag -n intermediate-cert --dps-name my-dps -g my-rg) && az iot dps certificate generate-verification-code --n intermediate-cert --dps-name my-dps -g my-rg -e $ETAG
The command failed with an unexpected error. Here is the traceback:
'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte
Traceback (most recent call last):
  File "/opt/az/lib/python3.6/site-packages/knack/cli.py", line 231, in invoke
    cmd_result = self.invocation.execute(args)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 657, in execute
    raise ex
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 720, in _run_jobs_serially
    results.append(self._run_job(expanded_arg, cmd_copy))
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 691, in _run_job
    result = cmd_copy(params)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 328, in __call__
    return self.handler(*args, **kwargs)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/command_operation.py", line 121, in handler
    return op(**command_args)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/command_modules/iot/custom.py", line 324, in iot_dps_certificate_gen_code
    response.properties.certificate = response.properties.certificate.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte

To Reproduce:

Steps to reproduce the behavior. Note that argument values have been redacted, as they may contain sensitive information.

Expected Behavior

Verification code is returned

Environment Summary

Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-debian-bullseye-sid, Ubuntu 20.04 LTS
Python 3.6.10
Installer: DEB

azure-cli 2.28.0 *

Workaround

Option 1: Downgrade to azure-cli 2.27 or earlier.

Option 2: Use az rest

ETAG=$(az iot dps certificate show -o json --query etag -n intermediate-cert --dps-name hv-dev-001-dps-01 -g hv-dev-iot) 
az rest --method post --url https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.Devices/provisioningServices/my-dps/certificates/intermediate-cert/generateVerificationCode?api-version=2020-03-01 --headers If-Match=$ETAG -o json`

Additional Context

The call to retrieve the certificate verification code is successful. However, the CLI tool fails to process it correctly. Here's the relevant debug output:

msrest.universal_http: Evaluate proxies against ENV settings: True
urllib3.connectionpool: Starting new HTTPS connection (1): management.azure.com:443
urllib3.connectionpool: https://management.azure.com:443 "POST /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.Devices/provisioningServices/my-dps/certificates/intermediate-cert/generateVerificationCode?api-version=2020-03-01 HTTP/1.1" 200 None
msrest.http_logger: Response status: 200
msrest.http_logger: Response headers:
msrest.http_logger:     'Cache-Control': 'no-cache'
msrest.http_logger:     'Pragma': 'no-cache'
msrest.http_logger:     'Transfer-Encoding': 'chunked'
msrest.http_logger:     'Content-Type': 'application/json; charset=utf-8'
msrest.http_logger:     'Content-Encoding': 'gzip'
msrest.http_logger:     'Expires': '-1'
msrest.http_logger:     'Vary': 'Accept-Encoding'
msrest.http_logger:     'Server': 'Microsoft-HTTPAPI/2.0'
msrest.http_logger:     'x-ms-ratelimit-remaining-subscription-writes': '1199'
msrest.http_logger:     'x-ms-request-id': 'c5c6c34a-c60f-4f95-917a-499537d729a8'
msrest.http_logger:     'x-ms-correlation-request-id': 'c5c6c34a-c60f-4f95-917a-499537d729a8'
msrest.http_logger:     'x-ms-routing-request-id': 'NORTHCENTRALUS:20211022T192537Z:c5c6c34a-c60f-4f95-917a-499537d729a8'
msrest.http_logger:     'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
msrest.http_logger:     'X-Content-Type-Options': 'nosniff'
msrest.http_logger:     'Date': 'Fri, 22 Oct 2021 19:25:36 GMT'
msrest.http_logger: Response content:
msrest.http_logger: {"properties":{"verificationCode":"0EE0E67D454B44D2EB8F4E4F2B40FF0007CB6DF526285842","subject":"Tier 3 Intermediate CA 02 for DEV TESTING","expiry":"Fri, 07 Aug 2026 23:54:27 GMT","thumbprint":"2A4030970D9AD911093113E8ED2E05D5D83055B9","isVerified":false,"created":"Mon, 01 Jan 0001 00:00:00 GMT","updated":"Fri, 22 Oct 2021 19:25:37 GMT","certificate":"(CERT DATA REMOVED, CONTACT ME IF YOU NEED IT AS AN EXAMPLE)"},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.Devices/provisioningServices/my-dps/certificates/intermediate-cert","name":"intermediate-cert","type":"Microsoft.Devices/provisioningServices/Certificates","etag":"IjJiMDE0MGExLTAwMDAtMDMwMC0wMDAwLTYxNzMxMDMxMDAwMCI="}
cli.azure.cli.core.util: azure.cli.core.util.handle_exception is called with an exception:
cli.azure.cli.core.util: Traceback (most recent call last):
  File "/opt/az/lib/python3.6/site-packages/knack/cli.py", line 231, in invoke
    cmd_result = self.invocation.execute(args)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 657, in execute
    raise ex
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 720, in _run_jobs_serially
    results.append(self._run_job(expanded_arg, cmd_copy))
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 691, in _run_job
    result = cmd_copy(params)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 328, in __call__
    return self.handler(*args, **kwargs)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/command_operation.py", line 121, in handler
    return op(**command_args)
  File "/opt/az/lib/python3.6/site-packages/azure/cli/command_modules/iot/custom.py", line 324, in iot_dps_certificate_gen_code
    response.properties.certificate = response.properties.certificate.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte
ghost commented 2 years ago

Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @Azure/azure-iot-cli-triage.

Issue Details
## Describe the bug As of 2.28 (does not occur in 2.27 or earlier) the command to generate a verification code for a DPS certificate fails with this error. **Command Name** `az iot dps certificate generate-verification-code` **Errors:** ``` ksykora@DESKTOP-LSEMUSR:~/ops-infrastructure-azure/v2/iothub$ ETAG=$(az iot dps certificate show -o json --query etag -n intermediate-cert --dps-name my-dps -g my-rg) && az iot dps certificate generate-verification-code --n intermediate-cert --dps-name my-dps -g my-rg -e $ETAG The command failed with an unexpected error. Here is the traceback: 'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte Traceback (most recent call last): File "/opt/az/lib/python3.6/site-packages/knack/cli.py", line 231, in invoke cmd_result = self.invocation.execute(args) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 657, in execute raise ex File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 720, in _run_jobs_serially results.append(self._run_job(expanded_arg, cmd_copy)) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 691, in _run_job result = cmd_copy(params) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 328, in __call__ return self.handler(*args, **kwargs) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/command_operation.py", line 121, in handler return op(**command_args) File "/opt/az/lib/python3.6/site-packages/azure/cli/command_modules/iot/custom.py", line 324, in iot_dps_certificate_gen_code response.properties.certificate = response.properties.certificate.decode('utf-8') UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte ``` ## To Reproduce: Steps to reproduce the behavior. Note that argument values have been redacted, as they may contain sensitive information. - Create a DPS instance (Portal or CLI, doesn't matter) - Upload a certificate to DPS (Portal or CLI, doesn't matter) -- make sure it's not verified - get the etag: `ETAG=$(az iot dps certificate show -o json --query etag -n intermediate-cert --dps-name my-dps -g my-rg)` - Get the verification code: `az iot dps certificate generate-verification-code --n intermediate-cert --dps-name my-dps -g my-rg -e $ETAG` ## Expected Behavior Verification code is returned ## Environment Summary ``` Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-debian-bullseye-sid, Ubuntu 20.04 LTS Python 3.6.10 Installer: DEB azure-cli 2.28.0 * ``` ## Workaround Option 1: Downgrade to azure-cli 2.27 or earlier. Option 2: Use `az rest` ```bash ETAG=$(az iot dps certificate show -o json --query etag -n intermediate-cert --dps-name hv-dev-001-dps-01 -g hv-dev-iot) az rest --method post --url https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.Devices/provisioningServices/my-dps/certificates/intermediate-cert/generateVerificationCode?api-version=2020-03-01 --headers If-Match=$ETAG -o json` ``` ## Additional Context The call to retrieve the certificate verification code is successful. However, the CLI tool fails to process it correctly. Here's the relevant debug output: ``` msrest.universal_http: Evaluate proxies against ENV settings: True urllib3.connectionpool: Starting new HTTPS connection (1): management.azure.com:443 urllib3.connectionpool: https://management.azure.com:443 "POST /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.Devices/provisioningServices/my-dps/certificates/intermediate-cert/generateVerificationCode?api-version=2020-03-01 HTTP/1.1" 200 None msrest.http_logger: Response status: 200 msrest.http_logger: Response headers: msrest.http_logger: 'Cache-Control': 'no-cache' msrest.http_logger: 'Pragma': 'no-cache' msrest.http_logger: 'Transfer-Encoding': 'chunked' msrest.http_logger: 'Content-Type': 'application/json; charset=utf-8' msrest.http_logger: 'Content-Encoding': 'gzip' msrest.http_logger: 'Expires': '-1' msrest.http_logger: 'Vary': 'Accept-Encoding' msrest.http_logger: 'Server': 'Microsoft-HTTPAPI/2.0' msrest.http_logger: 'x-ms-ratelimit-remaining-subscription-writes': '1199' msrest.http_logger: 'x-ms-request-id': 'c5c6c34a-c60f-4f95-917a-499537d729a8' msrest.http_logger: 'x-ms-correlation-request-id': 'c5c6c34a-c60f-4f95-917a-499537d729a8' msrest.http_logger: 'x-ms-routing-request-id': 'NORTHCENTRALUS:20211022T192537Z:c5c6c34a-c60f-4f95-917a-499537d729a8' msrest.http_logger: 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' msrest.http_logger: 'X-Content-Type-Options': 'nosniff' msrest.http_logger: 'Date': 'Fri, 22 Oct 2021 19:25:36 GMT' msrest.http_logger: Response content: msrest.http_logger: {"properties":{"verificationCode":"0EE0E67D454B44D2EB8F4E4F2B40FF0007CB6DF526285842","subject":"Tier 3 Intermediate CA 02 for DEV TESTING","expiry":"Fri, 07 Aug 2026 23:54:27 GMT","thumbprint":"2A4030970D9AD911093113E8ED2E05D5D83055B9","isVerified":false,"created":"Mon, 01 Jan 0001 00:00:00 GMT","updated":"Fri, 22 Oct 2021 19:25:37 GMT","certificate":"(CERT DATA REMOVED, CONTACT ME IF YOU NEED IT AS AN EXAMPLE)"},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.Devices/provisioningServices/my-dps/certificates/intermediate-cert","name":"intermediate-cert","type":"Microsoft.Devices/provisioningServices/Certificates","etag":"IjJiMDE0MGExLTAwMDAtMDMwMC0wMDAwLTYxNzMxMDMxMDAwMCI="} cli.azure.cli.core.util: azure.cli.core.util.handle_exception is called with an exception: cli.azure.cli.core.util: Traceback (most recent call last): File "/opt/az/lib/python3.6/site-packages/knack/cli.py", line 231, in invoke cmd_result = self.invocation.execute(args) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 657, in execute raise ex File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 720, in _run_jobs_serially results.append(self._run_job(expanded_arg, cmd_copy)) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 691, in _run_job result = cmd_copy(params) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/__init__.py", line 328, in __call__ return self.handler(*args, **kwargs) File "/opt/az/lib/python3.6/site-packages/azure/cli/core/commands/command_operation.py", line 121, in handler return op(**command_args) File "/opt/az/lib/python3.6/site-packages/azure/cli/command_modules/iot/custom.py", line 324, in iot_dps_certificate_gen_code response.properties.certificate = response.properties.certificate.decode('utf-8') UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte ```
Author: kensykora
Assignees: -
Labels: `Service Attention`, `customer-reported`, `IoTDPS`, `IoT`, `IoT/CLI`
Milestone: -
yonzhan commented 2 years ago

route to service team

c-ryan-k commented 2 years ago

@kensykora Thanks for bringing this to our attention - I'm taking a look now.

It looks like the issue is coming from us decoding the certificate property in the response from generating the verification code. The newest DPS SDK we added in this release added this property to the generate-verification-code response, but it is returned in a format that we need to decode in order to display correctly in JSON output.

I've tried a few certs I've used for testing on my own DPS without issue, so I'm guessing there's a difference in the formatting of your certificate body's encoding / formatting in the service response.

Could you provide the steps used to generate this intermediate cert to see if we can get a repro? Is the certificate body that was uploaded in a base64-encoded format? .PEM or .CER?

kensykora commented 2 years ago

The intermediate cert itself was generated with OpenSSL and uploaded to Key Vault. It is the third certificate in a chain that originates from a self-signed cert. I don't know what format it was uploaded in originally, but I'm not sure if it matters because I'd assume Key Vault converts into the same format when you export it.

I automate pulling the cert from key vault to upload to DPS. Terraform is used to download the certificate. (we use the terraform data "azurerm_key_vault_certificate" which exposes the certificate as certificate_data_base64 property) I'm not really clear on if this format is .cer or .pem. If I had to guess it's whatever the KeyVault API sends as REST for public certificate content. I don't really care as long as DPS accepts the certificate (which it does)

From the key vault base64 data, I pipe that base64 content to another terraform resource "azurerm_iothub_dps_certificate" with its certificate_content property.

I'd have to dig into the terraform azurerm provider source to get you any more detail than that.

Because the terraform azurerm provider doesn't have the capability to verify the cert, I use an az CLI workaround in a terraform local_exec provisioner to complete the verification step, which is where this bug surfaced.

I can provide you the public portion of the cert if you can provide me with a secure way to send it to you. But perhaps the key vault export to DPS import scenario I described can give you the information you need on the formatting question or to reproduce it.

I'm happy to provide any specific debug output with my specific scenario as well (raw REST response from az cli perhaps, just let me know)