func()

in commands/credential_scan.go [64:352]


func (c CredentialScanCommand) Execute() int {
	wd, err := os.Getwd()
	if err != nil {
		logrus.Errorf("failed to get working directory: %+v", err)
		return 1
	}
	if c.workingDir != "" {
		wd, err = filepath.Abs(c.workingDir)
		if err != nil {
			logrus.Errorf("working directory is invalid: %+v", err)
			return 1
		}
	}
	if c.swaggerRepoPath != "" {
		c.swaggerRepoPath, err = filepath.Abs(c.swaggerRepoPath)
		if err != nil {
			logrus.Errorf("swagger repo path %q is invalid: %+v", c.swaggerRepoPath, err)
			return 1
		}

		if _, err := os.Stat(c.swaggerRepoPath); os.IsNotExist(err) {
			logrus.Errorf("swagger repo path %q is invalid: path does not exist", c.swaggerRepoPath)
			return 1
		}

		c.swaggerRepoPath = strings.TrimSuffix(c.swaggerRepoPath, "/")

		if !strings.HasSuffix(c.swaggerRepoPath, "specification") {
			logrus.Errorf("swagger repo path %q is invalid: must point to \"specification\", e.g., /home/projects/azure-rest-api-specs/specification", c.swaggerRepoPath)
			return 1
		}

		c.swaggerRepoPath += "/"
	}
	if c.swaggerIndexFile != "" {
		c.swaggerIndexFile, err = filepath.Abs(c.swaggerIndexFile)
		if err != nil {
			logrus.Errorf("swagger index file path %q is invalid: %+v", c.swaggerIndexFile, err)
			return 1
		}

		if _, err := os.Stat(c.swaggerIndexFile); os.IsNotExist(err) {
			logrus.Infof("swagger index file %q does not exist, will try to build or download index", c.swaggerIndexFile)
		}
	}

	outputDir := wd
	if c.outputDir != "" {
		outputDir, err = filepath.Abs(c.outputDir)
		if err != nil {
			logrus.Errorf("output directory is invalid: %+v", err)
			return 1
		}

	}

	tfFiles, err := hcl.FindTfFiles(wd)
	if err != nil {
		logrus.Errorf("failed to find tf files for %q: %+v", wd, err)
		return 1
	}
	if len(*tfFiles) == 0 {
		logrus.Warnf("no tf file found in %q", wd)
	}
	logrus.Infof("find %v tf file(s) under %s", len(*tfFiles), wd)

	azapiResources := make([]hcl.AzapiResource, 0)
	vars := make(map[string]hcl.Variable, 0)
	azureProviders := make([]hcl.AzureProvider, 0)
	for _, tfFile := range *tfFiles {
		f, err := hcl.ParseHclFile(tfFile)
		if err != nil {
			logrus.Errorf("failed to parse hcl file %q: %+v", tfFile, err)
			return 1
		}

		azapiResourceInFile, err := hcl.ParseAzapiResource(*f)
		if err != nil {
			logrus.Errorf("failed to parse azapi resource for %q: %+v", tfFile, err)
			return 1
		}
		azapiResources = append(azapiResources, *azapiResourceInFile...)

		varsInFile, err := hcl.ParseVariables(*f)
		if err != nil {
			logrus.Errorf("failed to parse variables for %q: %+v", tfFile, err)
			return 1
		}

		for k, v := range *varsInFile {
			vars[k] = v
		}

		azureProvidersInFile, err := hcl.ParseAzureProvider(*f)
		if err != nil {
			logrus.Errorf("failed to parse azure provider for %q: %+v", tfFile, err)
			return 1
		}
		azureProviders = append(azureProviders, *azureProvidersInFile...)

	}

	credScanErrors := make([]CredScanError, 0)

	for _, azureProvider := range azureProviders {
		if v := azureProvider.SubscriptionId; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "subscription_id", v, vars)...)
		}

		if v := azureProvider.TenantId; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "tenant_id", v, vars)...)
		}

		if v := azureProvider.AuxiliaryTenantIds; len(v) > 0 {
			for i, tenant_id := range v {
				credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, fmt.Sprintf("auxiliary_tenant_ids[%v]", i), tenant_id, vars)...)
			}
		}

		if v := azureProvider.AuxiliaryTenantIdsString; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "auxiliary_tenant_ids", v, vars)...)
		}

		if v := azureProvider.ClientId; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "client_id", v, vars)...)
		}

		if v := azureProvider.ClientCertificate; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "client_certificate", v, vars)...)
		}

		if v := azureProvider.ClientCertificatePassword; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "client_certificate_password", v, vars)...)
		}

		if v := azureProvider.ClientSecret; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "client_secret", v, vars)...)
		}

		if v := azureProvider.OidcRequestToken; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "oidc_request_token", v, vars)...)
		}

		if v := azureProvider.OidcToken; v != "" {
			credScanErrors = append(credScanErrors, checkAzureProviderSecret(azureProvider, "oidc_token", v, vars)...)
		}

	}

	for _, azapiResource := range azapiResources {
		logrus.Infof("scaning azapi_resource.%s(%s)", azapiResource.Name, azapiResource.Type)

		if azapiResource.Body == "" {
			continue
		}
		var body interface{}
		err = json.Unmarshal([]byte(azapiResource.Body), &body)
		if err != nil {
			credScanErr := makeCredScanError(
				azapiResource,
				fmt.Sprintf("failed to unmarshal body: %+v", err),
				"",
			)
			credScanErrors = append(credScanErrors, credScanErr)
			logrus.Error(credScanErr)
			continue
		}

		mockedResourceId, apiVersion := coverage.MockResourceIDFromType(azapiResource.Type)
		logrus.Infof("azapi_resource.%s(%s): mocked possible resource ID: %s, API version: %s", azapiResource.Name, azapiResource.Type, mockedResourceId, apiVersion)

		var swaggerModel *coverage.SwaggerModel
		if c.swaggerRepoPath != "" {
			logrus.Infof("scan based on local swagger repo: %s", c.swaggerRepoPath)
			swaggerModel, err = coverage.GetModelInfoFromLocalIndex(mockedResourceId, apiVersion, "PUT", c.swaggerRepoPath, c.swaggerIndexFile)
			if err != nil {
				credScanErr := makeCredScanError(
					azapiResource,
					fmt.Sprintf("fail to find swagger model from local swagger with possible resource ID(%s) API version(%s): %+v", mockedResourceId, apiVersion, err),
					"",
				)
				credScanErrors = append(credScanErrors, credScanErr)
				logrus.Error(credScanErr)

				continue
			}
		} else {
			swaggerModel, err = coverage.GetModelInfoFromIndex(mockedResourceId, apiVersion, "PUT", c.swaggerIndexFile)
			if err != nil {
				credScanErr := makeCredScanError(
					azapiResource,
					fmt.Sprintf("fail to find swagger model with possible resource ID(%s) API version(%s): %+v", mockedResourceId, apiVersion, err),
					"",
				)
				credScanErrors = append(credScanErrors, credScanErr)
				logrus.Error(credScanErr)

				continue
			}
		}

		if swaggerModel == nil {
			credScanErr := makeCredScanError(
				azapiResource,
				fmt.Sprintf("unable to find swagger model with possible resource ID(%s) API version(%s)", mockedResourceId, apiVersion),
				"",
			)
			credScanErrors = append(credScanErrors, credScanErr)
			logrus.Error(credScanErr)

			continue
		}

		logrus.Infof("find swagger model for azapi_resource.%s(%s): %+v", azapiResource.Name, azapiResource.Type, *swaggerModel)

		model, err := coverage.Expand(swaggerModel.ModelName, swaggerModel.SwaggerPath)
		if err != nil {
			credScanErr := makeCredScanError(
				azapiResource,
				fmt.Sprintf("failed to expand model: %+v", err),
				"",
			)
			credScanErrors = append(credScanErrors, credScanErr)
			logrus.Error(credScanErr)

			continue
		}

		secrets := make(map[string]string)
		model.CredScan(body, secrets)

		logrus.Infof("find secrets for azapi_resource.%s(%s): %+v", azapiResource.Name, azapiResource.Type, secrets)

		for k, v := range secrets {
			if !strings.HasPrefix(v, "$") || strings.HasPrefix(v, "$local.") {
				credScanErr := makeCredScanError(
					azapiResource,
					"cannot use plain text or 'local' for secret, please follow https://github.com/Azure/armstrong/blob/main/docs/guidance-for-api-test.md#4-q-i-have-some-sensitive-information-in-the-test-case-how-to-hide-it to hide the secret values",
					k,
				)
				credScanErrors = append(credScanErrors, credScanErr)
				logrus.Error(credScanErr)

				continue
			}

			if strings.HasPrefix(v, "$var.") {
				varName := strings.TrimPrefix(v, "$var.")
				varName = strings.Split(varName, ".")[0]
				theVar, ok := vars[varName]
				if !ok {
					credScanErr := makeCredScanError(
						azapiResource,
						fmt.Sprintf("variable %q was not found, please follow https://github.com/Azure/armstrong/blob/main/docs/guidance-for-api-test.md#4-q-i-have-some-sensitive-information-in-the-test-case-how-to-hide-it to set the variable for secret values", varName),
						k,
					)
					credScanErrors = append(credScanErrors, credScanErr)
					logrus.Error(credScanErr)

					continue
				}

				if theVar.HasDefault {
					credScanErr := makeCredScanError(
						azapiResource,
						fmt.Sprintf("variable %q (%v:%v) used in secret field but has a default value, please follow https://github.com/Azure/armstrong/blob/main/docs/guidance-for-api-test.md#4-q-i-have-some-sensitive-information-in-the-test-case-how-to-hide-it to set the variable for secret values", varName, theVar.FileName, theVar.LineNumber),
						k,
					)
					credScanErrors = append(credScanErrors, credScanErr)
					logrus.Error(credScanErr)
				}

				if !theVar.IsSensitive {
					credScanErr := makeCredScanError(
						azapiResource,
						fmt.Sprintf("variable %q (%v:%v) used in secret field but is not marked as sensitive, please follow https://github.com/Azure/armstrong/blob/main/docs/guidance-for-api-test.md#4-q-i-have-some-sensitive-information-in-the-test-case-how-to-hide-it to set the variable for secret values", varName, theVar.FileName, theVar.LineNumber),
						k,
					)
					credScanErrors = append(credScanErrors, credScanErr)
					logrus.Error(credScanErr)
				}
			}
		}
	}

	storeCredScanErrors(outputDir, credScanErrors)

	return 0
}