Repository: smallstep/cli
Pull request #653 (2022-03-21 10:45:11)

Add other url formats for certificate inspect
Add other url formats with default port has requested in https://github.com/smallstep/cli/issues/459



Patch:
diff --git a/command/certificate/inspect.go b/command/certificate/inspect.go
index bdebbdd6..2f39aa58 100644
--- a/command/certificate/inspect.go
+++ b/command/certificate/inspect.go
@@ -72,6 +72,11 @@ Inspect a remote certificate (using the default root certificate bundle to verif
 $ step certificate inspect https://smallstep.com
 '''
 
+Inspect a remote certificate (using the standard port derived from the URL prefix):
+'''
+$ step certificate inspect smtps://smtp.gmail.com
+'''
+
 Inspect an invalid remote certificate:
 '''
 $ step certificate inspect --insecure https://expired.badssl.com
diff --git a/command/certificate/remote.go b/command/certificate/remote.go
index b070d6f2..f1d322a6 100644
--- a/command/certificate/remote.go
+++ b/command/certificate/remote.go
@@ -5,13 +5,20 @@ import (
 	"crypto/x509"
 	"net"
 	"net/url"
+	"strconv"
 	"strings"
 
 	"github.com/pkg/errors"
 	"github.com/smallstep/cli/crypto/x509util"
 )
 
-var urlPrefixes = []string{"https://", "tcp://", "tls://"}
+var urlPrefixes = map[string]uint16{
+	"tcp://":   443,
+	"tls://":   443,
+	"https://": 443,
+	"smtps://": 465,
+	"ldaps://": 636,
+}
 
 // getPeerCertificates creates a connection to a remote server and returns the
 // list of server certificates.
@@ -56,20 +63,27 @@ func getPeerCertificates(addr, serverName, roots string, insecure bool) ([]*x509
 // trimURL returns the host[:port] if the input is a URL, otherwise returns an
 // empty string (and 'isURL:false').
 //
+// If the URL is valid and no port is specified, the default port determined
+// by the URL prefix is used.
+//
 // Examples:
-// trimURL("https://smallstep.com/onbaording") -> "smallstep.com", true, nil
+// trimURL("https://smallstep.com/onbaording") -> "smallstep.com:443", true, nil
 // trimURL("https://ca.smallSTEP.com:8080") -> "ca.smallSTEP.com:8080", true, nil
 // trimURL("./certs/root_ca.crt") -> "", false, nil
-// trimURL("hTtPs://sMaLlStEp.cOm") -> "sMaLlStEp.cOm", true, nil
+// trimURL("hTtPs://sMaLlStEp.cOm") -> "sMaLlStEp.cOm:443", true, nil
 // trimURL("hTtPs://sMaLlStEp.cOm hello") -> "", false, err{"invalid url"}
 func trimURL(ref string) (string, bool, error) {
 	tmp := strings.ToLower(ref)
-	for _, prefix := range urlPrefixes {
+	for prefix := range urlPrefixes {
 		if strings.HasPrefix(tmp, prefix) {
 			u, err := url.Parse(ref)
 			if err != nil {
 				return "", false, errors.Wrapf(err, "error parsing URL '%s'", ref)
 			}
+			if _, _, err := net.SplitHostPort(u.Host); err != nil {
+				port := strconv.FormatUint(uint64(urlPrefixes[prefix]), 10)
+				u.Host = net.JoinHostPort(u.Host, port)
+			}
 			return u.Host, true, nil
 		}
 	}
diff --git a/make/common.mk b/make/common.mk
index c2798f13..18999304 100644
--- a/make/common.mk
+++ b/make/common.mk
@@ -17,7 +17,7 @@ OUTPUT_ROOT=output/
 
 bootstra%:
 	# Using a released version of golangci-lint to take into account custom replacements in their go.mod
-	$Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.42.0
+	$Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.45.2
 
 .PHONY: bootstra%
 
diff --git a/token/parse.go b/token/parse.go
index afdad9d6..8a005481 100644
--- a/token/parse.go
+++ b/token/parse.go
@@ -125,13 +125,14 @@ type AWSInstanceIdentityDocument struct {
 
 // azureXMSMirIDRegExp is the regular expression used to parse the xms_mirid claim.
 // Using case insensitive as resourceGroups appears as resourcegroups.
-var azureXMSMirIDRegExp = regexp.MustCompile(`(?i)^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`)
+var azureXMSMirIDRegExp = regexp.MustCompile(`(?i)^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.(Compute/virtualMachines|ManagedIdentity/userAssignedIdentities)/([^/]+)$`)
 
 // AzurePayload contains the information in the xms_mirid claim.
 type AzurePayload struct {
 	SubscriptionID string
 	ResourceGroup  string
-	VirtualMachine string
+	ResourceType   string
+	ResourceName   string
 }
 
 // Parse parses the given token verifying the signature with the key.
@@ -175,7 +176,8 @@ func parseResponse(jwt *jose.JSONWebToken, p Payload) (*JSONWebToken, error) {
 			p.Azure = &AzurePayload{
 				SubscriptionID: re[1],
 				ResourceGroup:  re[2],
-				VirtualMachine: re[3],
+				ResourceType:   strings.Split(re[3], "/")[1],
+				ResourceName:   re[4],
 			}
 		}
 	}
diff --git a/utils/cautils/certificate_flow.go b/utils/cautils/certificate_flow.go
index 6efc9aee..6cd664cb 100644
--- a/utils/cautils/certificate_flow.go
+++ b/utils/cautils/certificate_flow.go
@@ -271,7 +271,7 @@ func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject strin
 	case token.Azure:
 		if len(ips) == 0 && len(dnsNames) == 0 {
 			defaultSANs := []string{
-				jwt.Payload.Azure.VirtualMachine,
+				jwt.Payload.Azure.ResourceName,
 			}
 			if !sharedContext.DisableCustomSANs {
 				defaultSANs = append(defaultSANs, subject)


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 82fce2b6..73b0ed9b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -28,7 +28,7 @@ jobs:
         name: golangci-lint
         uses: golangci/golangci-lint-action@v2
         with:
-          version: 'v1.45.0'
+          version: 'v1.45.2'
           args: --timeout=30m
       -
         name: Test, Build
diff --git a/command/certificate/remote_test.go b/command/certificate/remote_test.go
index 54bcef36..91037745 100644
--- a/command/certificate/remote_test.go
+++ b/command/certificate/remote_test.go
@@ -15,12 +15,12 @@ func TestTrimURL(t *testing.T) {
 		err         error
 	}
 	tests := map[string]newTest{
-		"true-http":      {"https://smallstep.com", "smallstep.com", true, nil},
+		"true-http":      {"https://smallstep.com", "smallstep.com:443", true, nil},
 		"true-tcp":       {"tcp://smallstep.com:8080", "smallstep.com:8080", true, nil},
-		"true-tls":       {"tls://smallstep.com/onboarding", "smallstep.com", true, nil},
+		"true-tls":       {"tls://smallstep.com/onboarding", "smallstep.com:443", true, nil},
 		"false":          {"./certs/root_ca.crt", "", false, nil},
 		"false-err":      {"https://google.com hello", "", false, errors.New("error parsing URL 'https://google.com hello'")},
-		"true-http-case": {"hTtPs://sMaLlStEp.cOm", "sMaLlStEp.cOm", true, nil},
+		"true-http-case": {"hTtPs://sMaLlStEp.cOm", "sMaLlStEp.cOm:443", true, nil},
 	}
 
 	for name, tc := range tests {
diff --git a/token/parse_test.go b/token/parse_test.go
index caa7daf1..6e20ecbc 100644
--- a/token/parse_test.go
+++ b/token/parse_test.go
@@ -47,9 +47,13 @@ const (
 	  }
 	]
 }`
-	awsToken   = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbWF6b24iOnsiZG9jdW1lbnQiOiJld29nSUNKaFkyTnZkVzUwU1dRaU9pQWlTVzV0UWxOMGVuaDJjeUlzQ2lBZ0ltRnlZMmhwZEdWamRIVnlaU0k2SUNKNE9EWmZOalFpTEFvZ0lDSmhkbUZwYkdGaWFXeHBkSGxhYjI1bElqb2dJblZ6TFhkbGMzUXRNbUlpTEFvZ0lDSmlhV3hzYVc1blVISnZaSFZqZEhNaU9pQnVkV3hzTEFvZ0lDSmtaWFp3WVhsUWNtOWtkV04wUTI5a1pYTWlPaUJ1ZFd4c0xBb2dJQ0pwYldGblpVbGtJam9nSWxOQk5IaFpWVEZGWWtRaUxBb2dJQ0pwYm5OMFlXNWpaVWxrSWpvZ0lsb3pXRmRzV0VkT1dsSWlMQW9nSUNKcGJuTjBZVzVqWlZSNWNHVWlPaUFpZERJdWJXbGpjbThpTEFvZ0lDSnJaWEp1Wld4SlpDSTZJQ0lpTEFvZ0lDSndaVzVrYVc1blZHbHRaU0k2SUNJeU1ERTVMVEExTFRBNVZEQXhPakEwT2pJekxqVXlORFkyT1ZvaUxBb2dJQ0p3Y21sMllYUmxTWEFpT2lBaU1USTNMakF1TUM0eElpd0tJQ0FpY21GdFpHbHphMGxrSWpvZ0lpSXNDaUFnSW5KbFoybHZiaUk2SUNKMWN5MTNaWE4wTFRFaUxBb2dJQ0oyWlhKemFXOXVJam9nSWpJd01UY3RNRGt0TXpBaUNuMD0iLCJzaWduYXR1cmUiOiJKbkdlaWl2UzMrU1QxNGdFd2NCQS9MK0dHYkRyZm1iWmh3dU9tMjIrbFhqWjZzSUN4YUpRaFg1bmxyRDBrZUlvL2Y0dmRkT3FGTzhvUWNHd0J1YXl6aHFSVEt6cGpZVXBoSDFRMlJBUGl4dmcvZ0NSZG5YV0pETHpIRGhjTXdraCtvWnpFTkFuSTlhWGR4Vko4dGFKSlRHZ2lEVzdBcXYzK1JYZG4xcndiVVE9In0sImF1ZCI6WyJhd3M6TGdONHU4OUliNyJdLCJleHAiOjE1NTczNjQxNjMsImlhdCI6MTU1NzM2Mzg2MywiaXNzIjoiZWMyLmFtYXpvbmF3cy5jb20iLCJqdGkiOiI2ZDAyZDQ1MTEzODUzOTc3OTRmMmJkNjc0M2I1Nzc0MmIyNDMzYzE1Y2ZhNjEzZGNjYWYwNmNiMzUzOGY5YzJmIiwibmJmIjoxNTU3MzYzODYzLCJzYW5zIjpudWxsLCJzdWIiOiJaM1hXbFhHTlpSIn0.bP40Dk1YMQlNMK_XYVde93x53c92hCwYCsUp4tL7wrA"
+	awsToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbWF6b24iOnsiZG9jdW1lbnQiOiJld29nSUNKaFkyTnZkVzUwU1dRaU9pQWlTVzV0UWxOMGVuaDJjeUlzQ2lBZ0ltRnlZMmhwZEdWamRIVnlaU0k2SUNKNE9EWmZOalFpTEFvZ0lDSmhkbUZwYkdGaWFXeHBkSGxhYjI1bElqb2dJblZ6TFhkbGMzUXRNbUlpTEFvZ0lDSmlhV3hzYVc1blVISnZaSFZqZEhNaU9pQnVkV3hzTEFvZ0lDSmtaWFp3WVhsUWNtOWtkV04wUTI5a1pYTWlPaUJ1ZFd4c0xBb2dJQ0pwYldGblpVbGtJam9nSWxOQk5IaFpWVEZGWWtRaUxBb2dJQ0pwYm5OMFlXNWpaVWxrSWpvZ0lsb3pXRmRzV0VkT1dsSWlMQW9nSUNKcGJuTjBZVzVqWlZSNWNHVWlPaUFpZERJdWJXbGpjbThpTEFvZ0lDSnJaWEp1Wld4SlpDSTZJQ0lpTEFvZ0lDSndaVzVrYVc1blZHbHRaU0k2SUNJeU1ERTVMVEExTFRBNVZEQXhPakEwT2pJekxqVXlORFkyT1ZvaUxBb2dJQ0p3Y21sMllYUmxTWEFpT2lBaU1USTNMakF1TUM0eElpd0tJQ0FpY21GdFpHbHphMGxrSWpvZ0lpSXNDaUFnSW5KbFoybHZiaUk2SUNKMWN5MTNaWE4wTFRFaUxBb2dJQ0oyWlhKemFXOXVJam9nSWpJd01UY3RNRGt0TXpBaUNuMD0iLCJzaWduYXR1cmUiOiJKbkdlaWl2UzMrU1QxNGdFd2NCQS9MK0dHYkRyZm1iWmh3dU9tMjIrbFhqWjZzSUN4YUpRaFg1bmxyRDBrZUlvL2Y0dmRkT3FGTzhvUWNHd0J1YXl6aHFSVEt6cGpZVXBoSDFRMlJBUGl4dmcvZ0NSZG5YV0pETHpIRGhjTXdraCtvWnpFTkFuSTlhWGR4Vko4dGFKSlRHZ2lEVzdBcXYzK1JYZG4xcndiVVE9In0sImF1ZCI6WyJhd3M6TGdONHU4OUliNyJdLCJleHAiOjE1NTczNjQxNjMsImlhdCI6MTU1NzM2Mzg2MywiaXNzIjoiZWMyLmFtYXpvbmF3cy5jb20iLCJqdGkiOiI2ZDAyZDQ1MTEzODUzOTc3OTRmMmJkNjc0M2I1Nzc0MmIyNDMzYzE1Y2ZhNjEzZGNjYWYwNmNiMzUzOGY5YzJmIiwibmJmIjoxNTU3MzYzODYzLCJzYW5zIjpudWxsLCJzdWIiOiJaM1hXbFhHTlpSIn0.bP40Dk1YMQlNMK_XYVde93x53c92hCwYCsUp4tL7wrA"
+	// azureToken represents a token obtained by a VM from the Azure IMDS (where the resource type is Microsoft.Compute/virtualMachines)
 	azureToken = "eyJhbGciOiJFUzI1NiIsImtpZCI6IjY2YjQxZjFlOTNmYTM5YTQzMjVkNmFhYmNlZGYwODJhZjg0NGZiMjJlZTk1NDAzYmVmOWQ0ZGNmMzcwYjcxMGUiLCJ0eXAiOiJKV1QifQ.eyJhcHBpZCI6InRoZS1hcHBpZCIsImFwcGlkYWNyIjoidGhlLWFwcGlkYWNyIiwiYXVkIjpbImh0dHBzOi8vbWFuYWdlbWVudC5henVyZS5jb20vIl0sImV4cCI6MTU1NzM0NTY1OCwiaWF0IjoxNTU3MzQ1MzU4LCJpZHAiOiJ0aGUtaWRwIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvUDg3aEFXVUUwYy8iLCJqdGkiOiJ0aGUtanRpIiwibmJmIjoxNTU3MzQ1MzU4LCJvaWQiOiJ0aGUtb2lkIiwic3ViIjoic3ViamVjdCIsInRpZCI6IlA4N2hBV1VFMGMiLCJ2ZXIiOiJ0aGUtdmVyc2lvbiIsInhtc19taXJpZCI6Ii9zdWJzY3JpcHRpb25zL3N1YnNjcmlwdGlvbklEL3Jlc291cmNlR3JvdXBzL3Jlc291cmNlR3JvdXAvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21wdXRlL3ZpcnR1YWxNYWNoaW5lcy92aXJ0dWFsTWFjaGluZSJ9.U_NdLMXLztkYEn0RXYD394SiR-QpaJ8eoRFBu0FYQJ6zDflc-veAceFbLsTdihNE21zm7qYAkDqXlE0q3Y7Zdg"
-	badToken   = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyeyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
+	// azureTokenMI represents a token obtained by a ManagedIdentity from the Azure IMDS
+	// This type of token has a different resource id (specifically the resource type is Microsoft.ManagedIdentity/userAssignedIdentities)
+	azureTokenMI = "eyJhbGciOiJFUzI1NiIsImtpZCI6IjY2YjQxZjFlOTNmYTM5YTQzMjVkNmFhYmNlZGYwODJhZjg0NGZiMjJlZTk1NDAzYmVmOWQ0ZGNmMzcwYjcxMGUiLCJ0eXAiOiJKV1QifQ.eyJhcHBpZCI6InRoZS1hcHBpZCIsImFwcGlkYWNyIjoidGhlLWFwcGlkYWNyIiwiYXVkIjpbImh0dHBzOi8vbWFuYWdlbWVudC5henVyZS5jb20vIl0sImV4cCI6MTU1NzM0NTY1OCwiaWF0IjoxNTU3MzQ1MzU4LCJpZHAiOiJ0aGUtaWRwIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvUDg3aEFXVUUwYy8iLCJqdGkiOiJ0aGUtanRpIiwibmJmIjoxNTU3MzQ1MzU4LCJvaWQiOiJ0aGUtb2lkIiwic3ViIjoic3ViamVjdCIsInRpZCI6IlA4N2hBV1VFMGMiLCJ2ZXIiOiJ0aGUtdmVyc2lvbiIsInhtc19taXJpZCI6Ii9zdWJzY3JpcHRpb25zL3N1YnNjcmlwdGlvbklEL3Jlc291cmNlR3JvdXBzL3Jlc291cmNlR3JvdXAvcHJvdmlkZXJzL01pY3Jvc29mdC5NYW5hZ2VkSWRlbnRpdHkvdXNlckFzc2lnbmVkSWRlbnRpdGllcy9tYW5hZ2VkSWRlbnRpdHlOYW1lIn0K.U_NdLMXLztkYEn0RXYD394SiR-QpaJ8eoRFBu0FYQJ6zDflc-veAceFbLsTdihNE21zm7qYAkDqXlE0q3Y7Zdg"
+	badToken     = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyeyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
 )
 
 func TestParse(t *testing.T) {
@@ -239,7 +243,24 @@ func TestParseInsecure(t *testing.T) {
 			Azure: &AzurePayload{
 				SubscriptionID: "subscriptionID",
 				ResourceGroup:  "resourceGroup",
-				VirtualMachine: "virtualMachine",
+				ResourceType:   "virtualMachines",
+				ResourceName:   "virtualMachine",
+			},
+		}, false},
+		{"ok Azure", args{azureTokenMI}, Payload{
+			Claims:           mustJoseClaims(azureToken),
+			AppID:            "the-appid",
+			AppIDAcr:         "the-appidacr",
+			IdentityProvider: "the-idp",
+			ObjectID:         "the-oid",
+			TenantID:         "P87hAWUE0c",
+			Version:          "the-version",
+			XMSMirID:         "/subscriptions/subscriptionID/resourceGroups/resourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/managedIdentityName",
+			Azure: &AzurePayload{
+				SubscriptionID: "subscriptionID",
+				ResourceGroup:  "resourceGroup",
+				ResourceType:   "userAssignedIdentities",
+				ResourceName:   "managedIdentityName",
 			},
 		}, false},
 		{"fail bad token", args{"foobarzar"}, Payload{}, true},