From 0f8db18fbbe8e776b11fe39d6f1203a0547b12b4 Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Mon, 2 Jun 2025 14:44:42 +0000 Subject: [PATCH 1/9] initial sketch of migrate function --- cmd/migrate.go | 142 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 cmd/migrate.go diff --git a/cmd/migrate.go b/cmd/migrate.go new file mode 100644 index 0000000..503a16e --- /dev/null +++ b/cmd/migrate.go @@ -0,0 +1,142 @@ +// Copyright 2025 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +// type JSONImportableObject interface { +// keyfactor.KeyfactorApiPAMProviderTypeCreateRequest | +// keyfactor.CSSCMSDataModelModelsProvider +// } + +// const ( +// convertResponseMsg = "Converting PAM Provider response to JSON" +// ) + +type PAMParameterValue struct { + GUID string + Value string +} + +var migrateCmd = &cobra.Command{ + Use: "migrate", + Short: "Keyfactor Migration Tools.", + Long: `Migrating to new Types and Extension implementations in Keyfactor is possible but not currently automated + in the platform. This tool aims to assist in performing the necessary steps to migrate, in limited scenarios, + to new Extension implementations that have definitions that differ from prior releases.`, +} + +var migratePamCmd = &cobra.Command{ + Use: "pam", + Short: "Migrate existing PAM Provider usage to a new PAM Provider", + Long: "Migrate existing PAM Provider usage to a new PAM Provider", + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + isExperimental := true + + // load specified flags + migrateFrom, _ := cmd.Flags().GetString("from") + migrateTo, _ := cmd.Flags().GetString("to") + appendName, _ := cmd.Flags().GetString("append-name") + + // Debug + expEnabled checks + informDebug(debugFlag) + debugErr := warnExperimentalFeature(expEnabled, isExperimental) + if debugErr != nil { + return debugErr + } + + // Log flags + log.Info().Str("from", migrateFrom). + Str("to", migrateTo). + Str("append-name", appendName). + Msg("migrate PAM Provider") + + // Authenticate + sdkClient, cErr := initGenClient(false) + if cErr != nil { + return cErr + } + + log.Info().Msg("looking up usage of <> PAM Provider") + + log.Debug().Msg("call: PAMProviderGetPamProviders()") + listPamProvidersInUse, httpResponse, rErr := sdkClient.PAMProviderApi.PAMProviderGetPamProviders(context.Background()). + XKeyfactorRequestedWith(XKeyfactorRequestedWith).XKeyfactorApiVersion(XKeyfactorApiVersion). + PqQueryString(fmt.Sprintf("name -eq \"%s\"", migrateFrom)). + Execute() + log.Debug().Msg("returned: PAMProviderGetPamProviders()") + + if rErr != nil { + log.Error().Err(rErr).Send() + return returnHttpErr(httpResponse, rErr) + } + + // TODO: ensure only 1 returned PAM Provider definition + + // get PAM Type definition for PAM Provider migrating <> + log.Debug().Msg("call: PAMProviderGetPamProviders()") + pamTypes, httpResponse, rErr := sdkClient.PAMProviderApi.PAMProviderGetPamProviderTypes(context.Background()). + XKeyfactorRequestedWith(XKeyfactorRequestedWith).XKeyfactorApiVersion(XKeyfactorApiVersion). + Execute() + log.Debug().Msg("returned: PAMProviderGetPamProviders()") + + if rErr != nil { + log.Error().Err(rErr).Send() + return returnHttpErr(httpResponse, rErr) + } + + // assess <> source PAM Type to create map for storing existing data + // this map has the first string key record the parameter field name + // the inner map tracks PAM instance GUIDs to that instances value for the field + inUsePamParamValues := map[string]map[string]string{} + for _, pamType := range pamTypes { + if pamType.Id == listPamProvidersInUse[0].ProviderType.Id { + for _, pamParamType := range pamType.ProviderTypeParams { + if *pamParamType.InstanceLevel { + // found definition of an instance level param for the type in question + // create key in map for the field name + inUsePamParamValues[*pamParamType.Name] = map[string]string{} + } + } + } + } + + // step through list of every defined param value + // record unique GUIDs of every param value on InstanceLevel : true + // don't count InstanceLevel : false because those are Secret (DataType:2) instances for the Provider itself, not actual usages + for _, pamParam := range listPamProvidersInUse[0].ProviderTypeParamValues { + if *pamParam.ProviderTypeParam.InstanceLevel { + fieldName := *pamParam.ProviderTypeParam.Name + usageGuid := *pamParam.InstanceGuid + inUsePamParamValues[fieldName][usageGuid] = *pamParam.Value + } + } + + // TODO: make sure every field has the same number of GUIDs tracked + // tally GUID count for logging + + // log.Info().Msgf("Found %d PAM Provider usages of Provider %s",) + + // implement migration logic + // create new PAM Instance of designated <> type + }, +} From 7604303f2383245c24e61edcf70ae21323bbc9b3 Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:59:32 +0000 Subject: [PATCH 2/9] comment outline of migration; example json for migration API steps --- .../migrate-steps/step-1-get-provider.json | 127 +++++++++ .../migrate-steps/step-2-get-pam-types.json | 261 ++++++++++++++++++ .../step-3-post-new-provider.json | 22 ++ .../pam/migrate-steps/step-4-get-store.json | 35 +++ .../step-5-parse-properties-json.json | 110 ++++++++ .../step-6-update-secrets-new-pam-value.json | 34 +++ ...-put-store-update-with-new-properties.json | 35 +++ cmd/migrate.go | 69 +++-- 8 files changed, 675 insertions(+), 18 deletions(-) create mode 100644 artifacts/pam/migrate-steps/step-1-get-provider.json create mode 100644 artifacts/pam/migrate-steps/step-2-get-pam-types.json create mode 100644 artifacts/pam/migrate-steps/step-3-post-new-provider.json create mode 100644 artifacts/pam/migrate-steps/step-4-get-store.json create mode 100644 artifacts/pam/migrate-steps/step-5-parse-properties-json.json create mode 100644 artifacts/pam/migrate-steps/step-6-update-secrets-new-pam-value.json create mode 100644 artifacts/pam/migrate-steps/step-7-put-store-update-with-new-properties.json diff --git a/artifacts/pam/migrate-steps/step-1-get-provider.json b/artifacts/pam/migrate-steps/step-1-get-provider.json new file mode 100644 index 0000000..8d8dc4b --- /dev/null +++ b/artifacts/pam/migrate-steps/step-1-get-provider.json @@ -0,0 +1,127 @@ +[ + { + "Id": 6, + "Name": "migrate-from", + "Area": 1, + "ProviderType": { + "Id": "16fc28fa-d2e4-4152-9895-ebf261b011b5", + "Name": "CyberArk", + "ProviderTypeParams": [ + { + "Id": 1, + "Name": "Safe", + "DisplayName": "PrivateArk Safe", + "DataType": 1, + "InstanceLevel": false, + "ProviderType": null + }, + { + "Id": 2, + "Name": "Folder", + "DisplayName": "PrivateArk Folder Name", + "DataType": 1, + "InstanceLevel": true, + "ProviderType": null + }, + { + "Id": 3, + "Name": "Object", + "DisplayName": "PrivateArk Protected Password Name", + "DataType": 1, + "InstanceLevel": true, + "ProviderType": null + }, + { + "Id": 4, + "Name": "AppId", + "DisplayName": "Application ID", + "DataType": 1, + "InstanceLevel": false, + "ProviderType": null + } + ] + }, + "ProviderTypeParamValues": [ + { + "Id": 69, + "Value": "safe", + "InstanceId": null, + "InstanceGuid": null, + "ProviderTypeParam": { + "Id": 1, + "Name": "Safe", + "DisplayName": "PrivateArk Safe", + "DataType": 1, + "InstanceLevel": false + } + }, + { + "Id": 70, + "Value": "appid", + "InstanceId": null, + "InstanceGuid": null, + "ProviderTypeParam": { + "Id": 4, + "Name": "AppId", + "DisplayName": "Application ID", + "DataType": 1, + "InstanceLevel": false + } + }, + { + "Id": 71, + "Value": "folder1", + "InstanceId": null, + "InstanceGuid": "54b3dbea-4e2c-4cb2-9aa3-650b20dc8866", + "ProviderTypeParam": { + "Id": 2, + "Name": "Folder", + "DisplayName": "PrivateArk Folder Name", + "DataType": 1, + "InstanceLevel": true + } + }, + { + "Id": 72, + "Value": "name1", + "InstanceId": null, + "InstanceGuid": "54b3dbea-4e2c-4cb2-9aa3-650b20dc8866", + "ProviderTypeParam": { + "Id": 3, + "Name": "Object", + "DisplayName": "PrivateArk Protected Password Name", + "DataType": 1, + "InstanceLevel": true + } + }, + { + "Id": 73, + "Value": "folder3", + "InstanceId": null, + "InstanceGuid": "d64501b9-860f-4eaa-8c35-0b9594da759e", + "ProviderTypeParam": { + "Id": 2, + "Name": "Folder", + "DisplayName": "PrivateArk Folder Name", + "DataType": 1, + "InstanceLevel": true + } + }, + { + "Id": 74, + "Value": "name3", + "InstanceId": null, + "InstanceGuid": "d64501b9-860f-4eaa-8c35-0b9594da759e", + "ProviderTypeParam": { + "Id": 3, + "Name": "Object", + "DisplayName": "PrivateArk Protected Password Name", + "DataType": 1, + "InstanceLevel": true + } + } + ], + "SecuredAreaId": null, + "Remote": false + } +] \ No newline at end of file diff --git a/artifacts/pam/migrate-steps/step-2-get-pam-types.json b/artifacts/pam/migrate-steps/step-2-get-pam-types.json new file mode 100644 index 0000000..e12b5bc --- /dev/null +++ b/artifacts/pam/migrate-steps/step-2-get-pam-types.json @@ -0,0 +1,261 @@ +[ + { + "Id": "c3c99c92-e4c5-442e-8cc1-09bf3eaead9d", + "Name": "CyberArk-SdkCredentialProvider", + "Parameters": [ + { + "Id": 13, + "Name": "AppId", + "DisplayName": "Application ID", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 14, + "Name": "Safe", + "DisplayName": "Safe", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 15, + "Name": "Folder", + "DisplayName": "Folder", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 16, + "Name": "Object", + "DisplayName": "Object", + "DataType": 1, + "InstanceLevel": true + } + ] + }, + { + "Id": "9ef36d00-d85c-468b-8f38-275e7e5a7b56", + "Name": "CyberArk-CentralCredentialProvider", + "Parameters": [ + { + "Id": 21, + "Name": "AppId", + "DisplayName": "Application ID", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 22, + "Name": "Host", + "DisplayName": "CyberArk Host and Port", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 23, + "Name": "Site", + "DisplayName": "CyberArk API Site", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 24, + "Name": "Safe", + "DisplayName": "Safe", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 25, + "Name": "Folder", + "DisplayName": "Folder", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 26, + "Name": "Object", + "DisplayName": "Object", + "DataType": 1, + "InstanceLevel": true + } + ] + }, + { + "Id": "c09bbfa5-a081-4194-9dd2-31f3cc3fabcc", + "Name": "Thycotic", + "Parameters": [ + { + "Id": 5, + "Name": "SecretServerUrl", + "DisplayName": "Server URL", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 6, + "Name": "RuleName", + "DisplayName": "Rule Name", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 7, + "Name": "SecretId", + "DisplayName": "Thycotic Secret ID", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 8, + "Name": "RuleKey", + "DisplayName": "Rule Key", + "DataType": 2, + "InstanceLevel": false + } + ] + }, + { + "Id": "a23ee0a3-b56a-4016-b70e-8ca78e7c3789", + "Name": "Hashicorp-Vault", + "Parameters": [ + { + "Id": 27, + "Name": "Host", + "DisplayName": "Vault Host", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 28, + "Name": "Token", + "DisplayName": "Vault Token", + "DataType": 2, + "InstanceLevel": false + }, + { + "Id": 29, + "Name": "Path", + "DisplayName": "KV Engine Path", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 30, + "Name": "Secret", + "DisplayName": "KV Secret Name", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 31, + "Name": "Key", + "DisplayName": "KV Secret Key", + "DataType": 1, + "InstanceLevel": true + } + ] + }, + { + "Id": "74f21632-7581-4156-816b-deb2ce3f5e8e", + "Name": "1Password-CLI", + "Parameters": [ + { + "Id": 9, + "Name": "Vault", + "DisplayName": "1Password Secret Vault", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 10, + "Name": "Token", + "DisplayName": "1Password Service Account Token", + "DataType": 2, + "InstanceLevel": false + }, + { + "Id": 11, + "Name": "Item", + "DisplayName": "1Password Item Name", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 12, + "Name": "Field", + "DisplayName": "Field Name on Item", + "DataType": 1, + "InstanceLevel": true + } + ] + }, + { + "Id": "16fc28fa-d2e4-4152-9895-ebf261b011b5", + "Name": "CyberArk", + "Parameters": [ + { + "Id": 1, + "Name": "Safe", + "DisplayName": "PrivateArk Safe", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 2, + "Name": "Folder", + "DisplayName": "PrivateArk Folder Name", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 3, + "Name": "Object", + "DisplayName": "PrivateArk Protected Password Name", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 4, + "Name": "AppId", + "DisplayName": "Application ID", + "DataType": 1, + "InstanceLevel": false + } + ] + }, + { + "Id": "1ec1e6fc-5c5a-43d8-b285-fb343e6749a0", + "Name": "ApiTest", + "Parameters": [ + { + "Id": 17, + "Name": "Param1", + "DisplayName": "Param1", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 18, + "Name": "Param2", + "DisplayName": "Param2", + "DataType": 1, + "InstanceLevel": true + }, + { + "Id": 19, + "Name": "Param3", + "DisplayName": "Param3", + "DataType": 1, + "InstanceLevel": false + }, + { + "Id": 20, + "Name": "Param4", + "DisplayName": "Param4", + "DataType": 2, + "InstanceLevel": false + } + ] + } +] \ No newline at end of file diff --git a/artifacts/pam/migrate-steps/step-3-post-new-provider.json b/artifacts/pam/migrate-steps/step-3-post-new-provider.json new file mode 100644 index 0000000..09cf731 --- /dev/null +++ b/artifacts/pam/migrate-steps/step-3-post-new-provider.json @@ -0,0 +1,22 @@ +{ + "name": "migrate-from-to", + "remote": false, + "area": 1, + "providerType": { + "id": "c3c99c92-e4c5-442e-8cc1-09bf3eaead9d" + }, + "providerTypeParamValues": [ + { + "value": "appid", + "instanceId": null, + "instanceGuid": null, + "providerTypeParam": { + "id": 13, + "name": "AppId", + "displayName": "Application ID", + "instanceLevel": false + } + } + ], + "securedAreaId": null +} \ No newline at end of file diff --git a/artifacts/pam/migrate-steps/step-4-get-store.json b/artifacts/pam/migrate-steps/step-4-get-store.json new file mode 100644 index 0000000..094c7d5 --- /dev/null +++ b/artifacts/pam/migrate-steps/step-4-get-store.json @@ -0,0 +1,35 @@ +{ + "Id": "917bd052-1507-48b5-917d-dbe638890b77", + "ContainerId": null, + "DisplayName": "PAM_TEST - PAM_TEST", + "ClientMachine": "PAM_TEST", + "Storepath": "PAM_TEST", + "CertStoreInventoryJobId": null, + "CertStoreType": 115, + "Approved": true, + "CreateIfMissing": false, + "Properties": "{\"ServerUsername\":{\"Value\":null,\"SecretTypeGuid\":\"84372609-f5af-4a3f-8bc0-a4d4b9013b7b\",\"InstanceId\":null,\"InstanceGuid\":\"d4bf0131-8abc-4d61-bc88-8e17551c0fe8\",\"ProviderTypeParameterValues\":[{\"Id\":88,\"Value\":\"folder1\",\"ParameterId\":0,\"InstanceId\":null,\"InstanceGuid\":\"d4bf0131-8abc-4d61-bc88-8e17551c0fe8\",\"Provider\":null,\"ProviderTypeParam\":{\"Id\":2,\"Name\":\"Folder\",\"DisplayName\":\"PrivateArk Folder Name\",\"DataType\":1,\"InstanceLevel\":true,\"ProviderType\":null}},{\"Id\":89,\"Value\":\"name1\",\"ParameterId\":0,\"InstanceId\":null,\"InstanceGuid\":\"d4bf0131-8abc-4d61-bc88-8e17551c0fe8\",\"Provider\":null,\"ProviderTypeParam\":{\"Id\":3,\"Name\":\"Object\",\"DisplayName\":\"PrivateArk Protected Password Name\",\"DataType\":1,\"InstanceLevel\":true,\"ProviderType\":null}}],\"ProviderId\":6,\"IsManaged\":true,\"HasValue\":true},\"ServerPassword\":{\"Value\":\"********************\",\"SecretTypeGuid\":\"84372609-f5af-4a3f-8bc0-a4d4b9013b7b\",\"InstanceId\":null,\"InstanceGuid\":\"22faa8b0-9bcf-4bc0-99de-8168b467ba45\",\"ProviderTypeParameterValues\":[],\"ProviderId\":null,\"IsManaged\":false,\"HasValue\":true},\"ClientCertificate\":{\"Value\":null,\"SecretTypeGuid\":\"84372609-f5af-4a3f-8bc0-a4d4b9013b7b\",\"InstanceId\":null,\"InstanceGuid\":\"a1fa5afc-5057-42c5-b107-bb1420cc946c\",\"ProviderTypeParameterValues\":[{\"Id\":90,\"Value\":\"folder3\",\"ParameterId\":0,\"InstanceId\":null,\"InstanceGuid\":\"a1fa5afc-5057-42c5-b107-bb1420cc946c\",\"Provider\":null,\"ProviderTypeParam\":{\"Id\":2,\"Name\":\"Folder\",\"DisplayName\":\"PrivateArk Folder Name\",\"DataType\":1,\"InstanceLevel\":true,\"ProviderType\":null}},{\"Id\":91,\"Value\":\"name3\",\"ParameterId\":0,\"InstanceId\":null,\"InstanceGuid\":\"a1fa5afc-5057-42c5-b107-bb1420cc946c\",\"Provider\":null,\"ProviderTypeParam\":{\"Id\":3,\"Name\":\"Object\",\"DisplayName\":\"PrivateArk Protected Password Name\",\"DataType\":1,\"InstanceLevel\":true,\"ProviderType\":null}}],\"ProviderId\":6,\"IsManaged\":true,\"HasValue\":true},\"ClientCertificatePassword\":{\"Value\":null,\"SecretTypeGuid\":\"84372609-f5af-4a3f-8bc0-a4d4b9013b7b\",\"InstanceId\":null,\"InstanceGuid\":\"1b80d474-9368-4b52-b723-a9159e2c771c\",\"ProviderTypeParameterValues\":[],\"ProviderId\":null,\"IsManaged\":false,\"HasValue\":false},\"AzureCloud\":\"public\",\"ServerUseSsl\":\"true\"}", + "AgentId": "cc65a1f8-3db1-4c6a-90fc-3c874888cddf", + "AgentAssigned": true, + "ContainerName": null, + "InventorySchedule": null, + "ReenrollmentStatus": { + "Data": false, + "AgentId": null, + "Message": "Key generation is not supported by the agent for this store.", + "JobProperties": null, + "CustomAliasAllowed": 0, + "EntryParameters": [] + }, + "SetNewPasswordAllowed": false, + "Password": { + "Value": null, + "SecretTypeGuid": "fe957956-4c32-4144-b896-6f85532a0da1", + "InstanceId": null, + "InstanceGuid": "917bd052-1507-48b5-917d-dbe638890b77", + "ProviderTypeParameterValues": [], + "ProviderId": null, + "IsManaged": false, + "HasValue": false + } +} \ No newline at end of file diff --git a/artifacts/pam/migrate-steps/step-5-parse-properties-json.json b/artifacts/pam/migrate-steps/step-5-parse-properties-json.json new file mode 100644 index 0000000..c737c21 --- /dev/null +++ b/artifacts/pam/migrate-steps/step-5-parse-properties-json.json @@ -0,0 +1,110 @@ +{ + "ServerUsername": { + "Value": null, + "SecretTypeGuid": "84372609-f5af-4a3f-8bc0-a4d4b9013b7b", + "InstanceId": null, + "InstanceGuid": "d4bf0131-8abc-4d61-bc88-8e17551c0fe8", + "ProviderTypeParameterValues": [ + { + "Id": 88, + "Value": "folder1", + "ParameterId": 0, + "InstanceId": null, + "InstanceGuid": "d4bf0131-8abc-4d61-bc88-8e17551c0fe8", + "Provider": null, + "ProviderTypeParam": { + "Id": 2, + "Name": "Folder", + "DisplayName": "PrivateArk Folder Name", + "DataType": 1, + "InstanceLevel": true, + "ProviderType": null + } + }, + { + "Id": 89, + "Value": "name1", + "ParameterId": 0, + "InstanceId": null, + "InstanceGuid": "d4bf0131-8abc-4d61-bc88-8e17551c0fe8", + "Provider": null, + "ProviderTypeParam": { + "Id": 3, + "Name": "Object", + "DisplayName": "PrivateArk Protected Password Name", + "DataType": 1, + "InstanceLevel": true, + "ProviderType": null + } + } + ], + "ProviderId": 6, + "IsManaged": true, + "HasValue": true + }, + "ServerPassword": { + "Value": "********************", + "SecretTypeGuid": "84372609-f5af-4a3f-8bc0-a4d4b9013b7b", + "InstanceId": null, + "InstanceGuid": "22faa8b0-9bcf-4bc0-99de-8168b467ba45", + "ProviderTypeParameterValues": [], + "ProviderId": null, + "IsManaged": false, + "HasValue": true + }, + "ClientCertificate": { + "Value": null, + "SecretTypeGuid": "84372609-f5af-4a3f-8bc0-a4d4b9013b7b", + "InstanceId": null, + "InstanceGuid": "a1fa5afc-5057-42c5-b107-bb1420cc946c", + "ProviderTypeParameterValues": [ + { + "Id": 90, + "Value": "folder3", + "ParameterId": 0, + "InstanceId": null, + "InstanceGuid": "a1fa5afc-5057-42c5-b107-bb1420cc946c", + "Provider": null, + "ProviderTypeParam": { + "Id": 2, + "Name": "Folder", + "DisplayName": "PrivateArk Folder Name", + "DataType": 1, + "InstanceLevel": true, + "ProviderType": null + } + }, + { + "Id": 91, + "Value": "name3", + "ParameterId": 0, + "InstanceId": null, + "InstanceGuid": "a1fa5afc-5057-42c5-b107-bb1420cc946c", + "Provider": null, + "ProviderTypeParam": { + "Id": 3, + "Name": "Object", + "DisplayName": "PrivateArk Protected Password Name", + "DataType": 1, + "InstanceLevel": true, + "ProviderType": null + } + } + ], + "ProviderId": 6, + "IsManaged": true, + "HasValue": true + }, + "ClientCertificatePassword": { + "Value": null, + "SecretTypeGuid": "84372609-f5af-4a3f-8bc0-a4d4b9013b7b", + "InstanceId": null, + "InstanceGuid": "1b80d474-9368-4b52-b723-a9159e2c771c", + "ProviderTypeParameterValues": [], + "ProviderId": null, + "IsManaged": false, + "HasValue": false + }, + "AzureCloud": "public", + "ServerUseSsl": "true" +} \ No newline at end of file diff --git a/artifacts/pam/migrate-steps/step-6-update-secrets-new-pam-value.json b/artifacts/pam/migrate-steps/step-6-update-secrets-new-pam-value.json new file mode 100644 index 0000000..9a5dae7 --- /dev/null +++ b/artifacts/pam/migrate-steps/step-6-update-secrets-new-pam-value.json @@ -0,0 +1,34 @@ +{ + "ServerUsername": { + "value": { + "Provider": 9, + "Parameters": { + "Safe": "safe", + "Folder": "folder1-new", + "Object": "object1-new" + } + } + }, + "ServerPassword": { + "Value": { + "SecretValue": "********************" + } + }, + "ClientCertificate": { + "value": { + "Provider": 9, + "Parameters": { + "Safe": "safe", + "Folder": "folder3-new", + "Object": "object3-new" + } + } + }, + "ClientCertificatePassword": { + "Value": { + "SecretValue": null + } + }, + "AzureCloud": "public", + "ServerUseSsl": "true" +} \ No newline at end of file diff --git a/artifacts/pam/migrate-steps/step-7-put-store-update-with-new-properties.json b/artifacts/pam/migrate-steps/step-7-put-store-update-with-new-properties.json new file mode 100644 index 0000000..a290243 --- /dev/null +++ b/artifacts/pam/migrate-steps/step-7-put-store-update-with-new-properties.json @@ -0,0 +1,35 @@ +{ + "Id": "917bd052-1507-48b5-917d-dbe638890b77", + "ContainerId": null, + "DisplayName": "PAM_TEST - PAM_TEST", + "ClientMachine": "PAM_TEST", + "Storepath": "PAM_TEST", + "CertStoreInventoryJobId": null, + "CertStoreType": 115, + "Approved": true, + "CreateIfMissing": false, + "Properties": "{\"ServerUsername\":{\"value\":{\"Provider\":9,\"Parameters\":{\"Safe\":\"safe\",\"Folder\":\"folder1-new\",\"Object\":\"object1-new\"}}},\"ServerPassword\":{\"Value\":{\"SecretValue\":\"********************\"}},\"ClientCertificate\":{\"value\":{\"Provider\":9,\"Parameters\":{\"Safe\":\"safe\",\"Folder\":\"folder3-new\",\"Object\":\"object3-new\"}}},\"ClientCertificatePassword\":{\"Value\":{\"SecretValue\":null}},\"AzureCloud\":\"public\",\"ServerUseSsl\":\"true\"}", + "AgentId": "cc65a1f8-3db1-4c6a-90fc-3c874888cddf", + "AgentAssigned": true, + "ContainerName": null, + "InventorySchedule": null, + "ReenrollmentStatus": { + "Data": false, + "AgentId": null, + "Message": "Key generation is not supported by the agent for this store.", + "JobProperties": null, + "CustomAliasAllowed": 0, + "EntryParameters": [] + }, + "SetNewPasswordAllowed": false, + "Password": { + "Value": null, + "SecretTypeGuid": "fe957956-4c32-4144-b896-6f85532a0da1", + "InstanceId": null, + "InstanceGuid": "917bd052-1507-48b5-917d-dbe638890b77", + "ProviderTypeParameterValues": [], + "ProviderId": null, + "IsManaged": false, + "HasValue": false + } +} \ No newline at end of file diff --git a/cmd/migrate.go b/cmd/migrate.go index 503a16e..f1b4b5d 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -22,20 +22,6 @@ import ( "github.com/spf13/cobra" ) -// type JSONImportableObject interface { -// keyfactor.KeyfactorApiPAMProviderTypeCreateRequest | -// keyfactor.CSSCMSDataModelModelsProvider -// } - -// const ( -// convertResponseMsg = "Converting PAM Provider response to JSON" -// ) - -type PAMParameterValue struct { - GUID string - Value string -} - var migrateCmd = &cobra.Command{ Use: "migrate", Short: "Keyfactor Migration Tools.", @@ -53,9 +39,10 @@ var migratePamCmd = &cobra.Command{ isExperimental := true // load specified flags - migrateFrom, _ := cmd.Flags().GetString("from") - migrateTo, _ := cmd.Flags().GetString("to") - appendName, _ := cmd.Flags().GetString("append-name") + migrateFrom, _ := cmd.Flags().GetString("from") // defined pam provider + migrateTo, _ := cmd.Flags().GetString("to") // target pam provider typefffffff + appendName, _ := cmd.Flags().GetString("append-name") // text to append to <> name + // TODO: define stores flag to pass in GUIDs of stores to migrate // Debug + expEnabled checks informDebug(debugFlag) @@ -64,6 +51,8 @@ var migratePamCmd = &cobra.Command{ return debugErr } + // <> Pam Type must be CyberArk-SdkCredentialProvider + // Log flags log.Info().Str("from", migrateFrom). Str("to", migrateTo). @@ -107,6 +96,7 @@ var migratePamCmd = &cobra.Command{ // assess <> source PAM Type to create map for storing existing data // this map has the first string key record the parameter field name // the inner map tracks PAM instance GUIDs to that instances value for the field + // map[fieldname] -> map[InstanceGuid] = set value inUsePamParamValues := map[string]map[string]string{} for _, pamType := range pamTypes { if pamType.Id == listPamProvidersInUse[0].ProviderType.Id { @@ -136,7 +126,50 @@ var migratePamCmd = &cobra.Command{ // log.Info().Msgf("Found %d PAM Provider usages of Provider %s",) - // implement migration logic + // GET all PAM Types + // select array entry with matching Name field of <> + // mark GUID ID for pam type + // mark integer IDs for each Parameter type + + // POST new PAM Provider // create new PAM Instance of designated <> type + // set area = 1 or previous value + // name = old name plus append parameter + // providertype.id = Type GUID + // for all provider level values: + // set value to migrating value + // null for instanceid, instanceguid + // providertypeparam should be set to all matching values from GET TYPES + // ignoring datatype + + // foreach store GUID pass in as a parameter----- + // GET Store by GUID (instance GUID matches Store Id GUID) + // output some store info to confirm + + // parse Properties field into interactable object + + // foreach property key (properties is an object not an array) + // if value is an object, and object has an InstanceGuid + // property object is a match for a secret + // instead, can check if there is a ProviderId set, and if that + // matches integer id of original Provider <> + + // update property object + // foreach ProviderTypeParameterValues + // where ProviderTypeParam.Name = first map key (map is map[fieldname]map[InstanceGuid]value) + // create new PAM value for this secret + // json object: + // value: { + // provider: integer id of new provider + // Parameters: { + // fieldname: new value + // }} + // + // leave all other fields untouched + // IMPORTANT: other secret fields need to match value:{secretvalue:"*****" or secretvalue:null} + + // marshal json back to string for Properties field + // make sure quotes are escaped + // submit PUT for updating Store definition }, } From 18700b0b45b5f5641db6f97bc5a821b56367a0dd Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Tue, 17 Jun 2025 21:50:24 +0000 Subject: [PATCH 3/9] feat(migrate): WIP with debug output complete up to PAM value retrieval and mapping --- cmd/migrate.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index f1b4b5d..e593b0e 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -16,6 +16,7 @@ package cmd import ( "context" + "encoding/json" "fmt" "github.com/rs/zerolog/log" @@ -79,6 +80,9 @@ var migratePamCmd = &cobra.Command{ return returnHttpErr(httpResponse, rErr) } + jobject, _ := json.MarshalIndent(listPamProvidersInUse, "", " ") + fmt.Println(string(jobject)) + // TODO: ensure only 1 returned PAM Provider definition // get PAM Type definition for PAM Provider migrating <> @@ -93,33 +97,53 @@ var migratePamCmd = &cobra.Command{ return returnHttpErr(httpResponse, rErr) } + // jobject, _ = json.MarshalIndent(pamTypes, "", " ") + // fmt.Println(string(jobject)) + // assess <> source PAM Type to create map for storing existing data // this map has the first string key record the parameter field name // the inner map tracks PAM instance GUIDs to that instances value for the field // map[fieldname] -> map[InstanceGuid] = set value inUsePamParamValues := map[string]map[string]string{} for _, pamType := range pamTypes { - if pamType.Id == listPamProvidersInUse[0].ProviderType.Id { - for _, pamParamType := range pamType.ProviderTypeParams { - if *pamParamType.InstanceLevel { + if *pamType.Id == *listPamProvidersInUse[0].ProviderType.Id { + // TODO: remove debugging + jobject, _ := json.MarshalIndent(pamType, "", " ") + fmt.Println(string(jobject)) + jobject, _ = json.MarshalIndent(pamType.AdditionalProperties["Parameters"], "", " ") + fmt.Println(string(jobject)) + // TODO: check typing, have to access "Parameters" instead of ProviderTypeParams + for _, pamParamType := range pamType.AdditionalProperties["Parameters"].([]interface{}) { + jobject, _ := json.MarshalIndent(pamParamType, "", " ") + fmt.Println(string(jobject)) + if pamParamType.(map[string]interface{})["InstanceLevel"].(bool) { // found definition of an instance level param for the type in question // create key in map for the field name - inUsePamParamValues[*pamParamType.Name] = map[string]string{} + inUsePamParamValues[pamParamType.(map[string]interface{})["Name"].(string)] = map[string]string{} + fmt.Println("made it!") } } } } + jobject, _ = json.MarshalIndent(inUsePamParamValues, "", " ") + fmt.Println(string(jobject)) // step through list of every defined param value // record unique GUIDs of every param value on InstanceLevel : true // don't count InstanceLevel : false because those are Secret (DataType:2) instances for the Provider itself, not actual usages for _, pamParam := range listPamProvidersInUse[0].ProviderTypeParamValues { + jobject, _ = json.MarshalIndent(pamParam, "", " ") + fmt.Println(string(jobject)) if *pamParam.ProviderTypeParam.InstanceLevel { fieldName := *pamParam.ProviderTypeParam.Name usageGuid := *pamParam.InstanceGuid inUsePamParamValues[fieldName][usageGuid] = *pamParam.Value } } + jobject, _ = json.MarshalIndent(inUsePamParamValues, "", " ") + fmt.Println(string(jobject)) + + return nil // TODO: make sure every field has the same number of GUIDs tracked // tally GUID count for logging @@ -173,3 +197,41 @@ var migratePamCmd = &cobra.Command{ // submit PUT for updating Store definition }, } + +func init() { + var from string + var to string + var appendName string + + RootCmd.AddCommand(migrateCmd) + + migrateCmd.AddCommand(migratePamCmd) + + migratePamCmd.Flags().StringVarP( + &from, + "from", + "f", + "", + "Name of the defined PAM Provider to migrate to a new type", + ) + + migratePamCmd.Flags().StringVarP( + &to, + "to", + "t", + "", + "Name of the PAM Provider Type to migrate to", + ) + + migratePamCmd.Flags().StringVarP( + &appendName, + "append-name", + "a", + "", + "Text to append to current PAM Provider Name in newly created Provider", + ) + + migratePamCmd.MarkFlagRequired("from") + migratePamCmd.MarkFlagRequired("to") + migratePamCmd.MarkFlagRequired("append-name") +} From d3fe4060203c1fe9d7cc52e392f05a5d484480d5 Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Wed, 25 Jun 2025 04:42:19 +0000 Subject: [PATCH 4/9] feat(migrate): WIP all data retrieved and transformed --- cmd/migrate.go | 247 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 225 insertions(+), 22 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index e593b0e..72621b7 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -19,6 +19,8 @@ import ( "encoding/json" "fmt" + // "github.com/Keyfactor/keyfactor-go-client-sdk/v24/api/keyfactor/v2" + "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/keyfactor" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -41,9 +43,9 @@ var migratePamCmd = &cobra.Command{ // load specified flags migrateFrom, _ := cmd.Flags().GetString("from") // defined pam provider - migrateTo, _ := cmd.Flags().GetString("to") // target pam provider typefffffff + migrateTo, _ := cmd.Flags().GetString("to") // target pam provider type appendName, _ := cmd.Flags().GetString("append-name") // text to append to <> name - // TODO: define stores flag to pass in GUIDs of stores to migrate + storeUsingPam, _ := cmd.Flags().GetString("store") // Debug + expEnabled checks informDebug(debugFlag) @@ -66,6 +68,9 @@ var migratePamCmd = &cobra.Command{ return cErr } + // TODO: assign error and check + legacyClient, _ := initClient(false) + log.Info().Msg("looking up usage of <> PAM Provider") log.Debug().Msg("call: PAMProviderGetPamProviders()") @@ -84,6 +89,7 @@ var migratePamCmd = &cobra.Command{ fmt.Println(string(jobject)) // TODO: ensure only 1 returned PAM Provider definition + fromPamProvider := listPamProvidersInUse[0] // get PAM Type definition for PAM Provider migrating <> log.Debug().Msg("call: PAMProviderGetPamProviders()") @@ -105,46 +111,66 @@ var migratePamCmd = &cobra.Command{ // the inner map tracks PAM instance GUIDs to that instances value for the field // map[fieldname] -> map[InstanceGuid] = set value inUsePamParamValues := map[string]map[string]string{} + fromProviderLevelParamValues := map[string]string{} + var fromPamType keyfactor.CSSCMSDataModelModelsProviderType + var toPamType keyfactor.CSSCMSDataModelModelsProviderType for _, pamType := range pamTypes { - if *pamType.Id == *listPamProvidersInUse[0].ProviderType.Id { + if *pamType.Id == *fromPamProvider.ProviderType.Id { + fromPamType = pamType // TODO: remove debugging + fmt.Println("vvv FROM TYPE vvv") jobject, _ := json.MarshalIndent(pamType, "", " ") fmt.Println(string(jobject)) jobject, _ = json.MarshalIndent(pamType.AdditionalProperties["Parameters"], "", " ") fmt.Println(string(jobject)) - // TODO: check typing, have to access "Parameters" instead of ProviderTypeParams - for _, pamParamType := range pamType.AdditionalProperties["Parameters"].([]interface{}) { - jobject, _ := json.MarshalIndent(pamParamType, "", " ") - fmt.Println(string(jobject)) - if pamParamType.(map[string]interface{})["InstanceLevel"].(bool) { - // found definition of an instance level param for the type in question - // create key in map for the field name - inUsePamParamValues[pamParamType.(map[string]interface{})["Name"].(string)] = map[string]string{} - fmt.Println("made it!") - } - } + fmt.Println("^^^ FROM TYPE ^^^") + } + + if *pamType.Name == migrateTo { + toPamType = pamType + // TODO: remove debugging + fmt.Println("vvv TO TYPE vvv") + jobject, _ := json.MarshalIndent(pamType, "", " ") + fmt.Println(string(jobject)) + jobject, _ = json.MarshalIndent(pamType.AdditionalProperties["Parameters"], "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ TO TYPE ^^^") + } + } + + // TODO: check typing, have to access "Parameters" instead of ProviderTypeParams + for _, pamParamType := range fromPamType.AdditionalProperties["Parameters"].([]interface{}) { + jobject, _ := json.MarshalIndent(pamParamType, "", " ") + fmt.Println(string(jobject)) + if pamParamType.(map[string]interface{})["InstanceLevel"].(bool) { + // found definition of an instance level param for the type in question + // create key in map for the field name + inUsePamParamValues[pamParamType.(map[string]interface{})["Name"].(string)] = map[string]string{} + fmt.Println("made it!") } } + jobject, _ = json.MarshalIndent(inUsePamParamValues, "", " ") fmt.Println(string(jobject)) // step through list of every defined param value // record unique GUIDs of every param value on InstanceLevel : true - // don't count InstanceLevel : false because those are Secret (DataType:2) instances for the Provider itself, not actual usages + // InstanceLevel : true is for per-secret fields + // InstanceLevel : false is provider level secrets - these are also recorded for migration for _, pamParam := range listPamProvidersInUse[0].ProviderTypeParamValues { - jobject, _ = json.MarshalIndent(pamParam, "", " ") - fmt.Println(string(jobject)) + // jobject, _ = json.MarshalIndent(pamParam, "", " ") + // fmt.Println(string(jobject)) + fieldName := *pamParam.ProviderTypeParam.Name + usageGuid := *pamParam.InstanceGuid if *pamParam.ProviderTypeParam.InstanceLevel { - fieldName := *pamParam.ProviderTypeParam.Name - usageGuid := *pamParam.InstanceGuid inUsePamParamValues[fieldName][usageGuid] = *pamParam.Value + } else { + fromProviderLevelParamValues[fieldName] = *pamParam.Value } } jobject, _ = json.MarshalIndent(inUsePamParamValues, "", " ") fmt.Println(string(jobject)) - return nil - // TODO: make sure every field has the same number of GUIDs tracked // tally GUID count for logging @@ -155,6 +181,55 @@ var migratePamCmd = &cobra.Command{ // mark GUID ID for pam type // mark integer IDs for each Parameter type + // TODO: check that migration target PAM Provider was not already created + + fmt.Println("creating new Provider of migration target PAM Type") + var migrationPamProvider keyfactor.CSSCMSDataModelModelsProvider + migrationPamProvider.Name = fromPamProvider.Name + appendName + migrationPamProvider.ProviderType = keyfactor.CSSCMSDataModelModelsProviderType{ + Id: toPamType.Id, + } + var onevalue int32 = 1 + migrationPamProvider.Area = &onevalue + migrationPamProvider.SecuredAreaId = nil + + // need to init AdditionalProperties map when setting value + migrationPamProvider.AdditionalProperties = map[string]interface{}{ + "Remote": false, // this property is not on the model for some reason + } + + fmt.Println("getting migration target PAM Type parameter definitions, InstanceLevel : false") + // TODO: check typing, have to access "Parameters" instead of ProviderTypeParams + for _, pamParamType := range fromPamType.AdditionalProperties["Parameters"].([]interface{}) { + if !pamParamType.(map[string]interface{})["InstanceLevel"].(bool) { + // found a provider level parameter + // need to find the value to map over + // then create an object with that value and TypeParam settings + paramName := pamParamType.(map[string]interface{})["Name"].(string) + paramValue := selectProviderParamValue(paramName, fromPamProvider.ProviderTypeParamValues) + paramTypeId := selectProviderTypeParamId(paramName, toPamType.AdditionalProperties["Parameters"].([]interface{})) + falsevalue := false + providerLevelParameter := keyfactor.CSSCMSDataModelModelsPamProviderTypeParamValue{ + Value: ¶mValue, + ProviderTypeParam: &keyfactor.CSSCMSDataModelModelsProviderTypeParam{ + Id: ¶mTypeId, + Name: ¶mName, + InstanceLevel: &falsevalue, + }, + } + // TODO: might need to explicit filter for CyberArk expected params, i.e. not map over Safe + // append filled out provider type parameter object, which contains the Provider-level parameter values + migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) + } + } + + msg := "Created new PAM Provider definition to be created." + fmt.Println(msg) + log.Info().Msg(msg) + jobject, _ = json.MarshalIndent(migrationPamProvider, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ PAM Provider to be created ^^^") + // POST new PAM Provider // create new PAM Instance of designated <> type // set area = 1 or previous value @@ -166,11 +241,24 @@ var migratePamCmd = &cobra.Command{ // providertypeparam should be set to all matching values from GET TYPES // ignoring datatype + // + // TODO: POST PAM PROVIDER + // + // foreach store GUID pass in as a parameter----- // GET Store by GUID (instance GUID matches Store Id GUID) // output some store info to confirm - // parse Properties field into interactable object + // TODO: assign error and check + certStore, _ := legacyClient.GetCertificateStoreByID(storeUsingPam) + + jobject, _ = json.MarshalIndent(certStore, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ found cert store ^^^") + + jobject, _ = json.MarshalIndent(certStore.Properties, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ cert store properties ^^^") // foreach property key (properties is an object not an array) // if value is an object, and object has an InstanceGuid @@ -178,6 +266,42 @@ var migratePamCmd = &cobra.Command{ // instead, can check if there is a ProviderId set, and if that // matches integer id of original Provider <> + for propName, prop := range certStore.Properties { + propSecret, isSecret := prop.(map[string]interface{}) + if isSecret { + formattedSecret := map[string]map[string]interface{}{ + "Value": map[string]interface{}{}, + } + isManaged := propSecret["IsManaged"].(bool) + if isManaged { // managed secret, i.e. PAM Provider in use + + // check if Pam Secret is using our migrating provider + if *fromPamProvider.Id == int32(propSecret["ProviderId"].(float64)) { + // reformat to required POST format for properties + formattedSecret["Value"] = reformatPamSecretForPost(propSecret) + } else { + // Pam Secret that Needs to be migrated + formattedSecret["Value"] = buildMigratedPamSecret(propSecret, fromProviderLevelParamValues, 0) + } + } else { + // non-managed secret i.e. a KF-encrypted secret, or no value + // still needs to be reformatted to required POST format + formattedSecret["Value"] = map[string]interface{}{ + "SecretValue": propSecret["Value"], + } + } + + // update Properties object with newly formatted secret, compliant with POST requirements + certStore.Properties[propName] = formattedSecret + + jobject, _ = json.MarshalIndent(certStore.Properties, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ SECRETS REFORMATTED ^^^") + } + } + + return nil + // update property object // foreach ProviderTypeParameterValues // where ProviderTypeParam.Name = first map key (map is map[fieldname]map[InstanceGuid]value) @@ -198,10 +322,80 @@ var migratePamCmd = &cobra.Command{ }, } +func selectProviderParamValue(name string, providerParameters []keyfactor.CSSCMSDataModelModelsPamProviderTypeParamValue) string { + for _, parameter := range providerParameters { + if name == *parameter.ProviderTypeParam.Name { + return *parameter.Value + } + } + return "NOTFOUND" // TODO: throw error when not found +} + +// TODO(check, remove): might need to select DisplayName as well, if required for input in API +func selectProviderTypeParamId(name string, pamTypeParameterDefinitions []interface{}) int32 { + for _, parameterDefinition := range pamTypeParameterDefinitions { + if name == parameterDefinition.(map[string]interface{})["Name"].(string) { + return int32(parameterDefinition.(map[string]interface{})["Id"].(float64)) // interface returns value as float64, needs to be cast to int32 + } + } + + return -1 // TODO: throw error when not found +} + +func reformatPamSecretForPost(secretProp map[string]interface{}) map[string]interface{} { + reformatted := map[string]interface{}{ + "Provider": secretProp["ProviderId"], + } + + providerParams := secretProp["ProviderTypeParameterValues"].([]interface{}) + reformattedParams := map[string]string{} + + for _, param := range providerParams { + providerTypeParam := param.(map[string]interface{})["ProviderTypeParam"].(map[string]interface{}) + name := providerTypeParam["Name"].(string) + value := param.(map[string]interface{})["Value"].(string) + reformattedParams[name] = value + } + + reformatted["Parameters"] = reformattedParams + return reformatted +} + +// Inputs: +// secretProp: existing Pam config for property +// migratingValues: map of existing values for matched GUID of this field +// fromProvider: previous provider, to get type level values +// pamProvider: newly created Pam Provider for the migration, with Provider Id +func buildMigratedPamSecret(secretProp map[string]interface{}, fromProviderLevelValues map[string]string, providerId int32) map[string]interface{} { + migrated := map[string]interface{}{ + "Provider": providerId, + } + + providerParams := secretProp["ProviderTypeParameterValues"].([]interface{}) + reformattedParams := map[string]string{} + + // NOTE: this is making an assumption that the property names have not changed + // and should be mapped back to the same value + for _, param := range providerParams { + providerTypeParam := param.(map[string]interface{})["ProviderTypeParam"].(map[string]interface{}) + name := providerTypeParam["Name"].(string) + value := param.(map[string]interface{})["Value"].(string) + reformattedParams[name] = value + } + + // TODO: this logic needs to not be hard-coded, and evaluated for actual migrations other than legacy CyberArk + reformattedParams["Safe"] = fromProviderLevelValues["Safe"] + + migrated["Properties"] = reformattedParams + + return migrated +} + func init() { var from string var to string var appendName string + var store string RootCmd.AddCommand(migrateCmd) @@ -231,7 +425,16 @@ func init() { "Text to append to current PAM Provider Name in newly created Provider", ) + migratePamCmd.Flags().StringVarP( + &store, + "store", + "s", + "", + "GUID of a Certificate Store, using a PAM Provider that should be migrated", + ) + migratePamCmd.MarkFlagRequired("from") migratePamCmd.MarkFlagRequired("to") migratePamCmd.MarkFlagRequired("append-name") + migratePamCmd.MarkFlagRequired("store") } From 2d4cfbddc33a9d2b8a0433e5a9aa2ae8e7b9ae98 Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Wed, 25 Jun 2025 06:24:28 +0000 Subject: [PATCH 5/9] feat(migrate): perform data operations to migrate legacy CyberArk PAM usage --- cmd/migrate.go | 96 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index 72621b7..8027e56 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -21,6 +21,7 @@ import ( // "github.com/Keyfactor/keyfactor-go-client-sdk/v24/api/keyfactor/v2" "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/keyfactor" + "github.com/Keyfactor/keyfactor-go-client/v3/api" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -217,9 +218,15 @@ var migratePamCmd = &cobra.Command{ InstanceLevel: &falsevalue, }, } - // TODO: might need to explicit filter for CyberArk expected params, i.e. not map over Safe + // append filled out provider type parameter object, which contains the Provider-level parameter values - migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) + // migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) + + // TODO: need to explicit filter for CyberArk expected params, i.e. not map over Safe + // this needs to be done programatically for other provider types + if paramName == "AppId" { + migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) + } } } @@ -241,16 +248,31 @@ var migratePamCmd = &cobra.Command{ // providertypeparam should be set to all matching values from GET TYPES // ignoring datatype - // - // TODO: POST PAM PROVIDER - // + createdPamProvider, httpResponse, rErr := sdkClient.PAMProviderApi.PAMProviderCreatePamProvider(context.Background()). + Provider(migrationPamProvider). + XKeyfactorRequestedWith(XKeyfactorRequestedWith).XKeyfactorApiVersion(XKeyfactorApiVersion). + Execute() + + if rErr != nil { + log.Error().Err(rErr).Send() + return returnHttpErr(httpResponse, rErr) + } + + fmt.Println("vvv CREATED MIGRATION PAM PROVIDER vvv") + jobject, _ = json.MarshalIndent(createdPamProvider, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ CREATED MIGRATION PAM PROVIDER ^^^") // foreach store GUID pass in as a parameter----- // GET Store by GUID (instance GUID matches Store Id GUID) // output some store info to confirm - // TODO: assign error and check - certStore, _ := legacyClient.GetCertificateStoreByID(storeUsingPam) + // TODO: use updated client when API endpoint available + certStore, rErr := legacyClient.GetCertificateStoreByID(storeUsingPam) + if rErr != nil { + log.Error().Err(rErr).Send() + return rErr + } jobject, _ = json.MarshalIndent(certStore, "", " ") fmt.Println(string(jobject)) @@ -277,11 +299,11 @@ var migratePamCmd = &cobra.Command{ // check if Pam Secret is using our migrating provider if *fromPamProvider.Id == int32(propSecret["ProviderId"].(float64)) { + // Pam Secret that Needs to be migrated + formattedSecret["Value"] = buildMigratedPamSecret(propSecret, fromProviderLevelParamValues, *createdPamProvider.Id) + } else { // reformat to required POST format for properties formattedSecret["Value"] = reformatPamSecretForPost(propSecret) - } else { - // Pam Secret that Needs to be migrated - formattedSecret["Value"] = buildMigratedPamSecret(propSecret, fromProviderLevelParamValues, 0) } } else { // non-managed secret i.e. a KF-encrypted secret, or no value @@ -293,32 +315,42 @@ var migratePamCmd = &cobra.Command{ // update Properties object with newly formatted secret, compliant with POST requirements certStore.Properties[propName] = formattedSecret - - jobject, _ = json.MarshalIndent(certStore.Properties, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ SECRETS REFORMATTED ^^^") } } - return nil + jobject, _ = json.MarshalIndent(certStore.Properties, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ SECRETS REFORMATTED ^^^") + + // propertiesAsString, _ := json.Marshal(certStore.Properties) + // jsonProps := string(propertiesAsString) + // escapedProps := strings.ReplaceAll(jsonProps, "\"", "\\\"") + // fmt.Println(escapedProps) // update property object - // foreach ProviderTypeParameterValues - // where ProviderTypeParam.Name = first map key (map is map[fieldname]map[InstanceGuid]value) - // create new PAM value for this secret - // json object: - // value: { - // provider: integer id of new provider - // Parameters: { - // fieldname: new value - // }} - // - // leave all other fields untouched - // IMPORTANT: other secret fields need to match value:{secretvalue:"*****" or secretvalue:null} - - // marshal json back to string for Properties field - // make sure quotes are escaped - // submit PUT for updating Store definition + // set required fields, and new Properties + updateStoreArgs := api.UpdateStoreFctArgs{ + Id: certStore.Id, + ClientMachine: certStore.ClientMachine, + StorePath: certStore.StorePath, + AgentId: certStore.AgentId, + Properties: certStore.Properties, + Password: &certStore.Password, + } + + // TODO: use updated client when API endpoint available + updatedStore, rErr := legacyClient.UpdateStore(&updateStoreArgs) + + if rErr != nil { + log.Error().Err(rErr).Send() + return rErr + } + + jobject, _ = json.MarshalIndent(updatedStore, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ UPDATED STORE ^^^") + + return nil }, } @@ -386,7 +418,7 @@ func buildMigratedPamSecret(secretProp map[string]interface{}, fromProviderLevel // TODO: this logic needs to not be hard-coded, and evaluated for actual migrations other than legacy CyberArk reformattedParams["Safe"] = fromProviderLevelValues["Safe"] - migrated["Properties"] = reformattedParams + migrated["Parameters"] = reformattedParams return migrated } From 955598ef9fa934c8e1da89b2e340b93b0ff1726e Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:11:34 +0000 Subject: [PATCH 6/9] feat(migrate): re-use existing migration target pam provider if it exists --- cmd/migrate.go | 225 +++++++++++++++++++++++++++++-------------------- 1 file changed, 133 insertions(+), 92 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index 8027e56..7d8de88 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -17,6 +17,7 @@ package cmd import ( "context" "encoding/json" + "errors" "fmt" // "github.com/Keyfactor/keyfactor-go-client-sdk/v24/api/keyfactor/v2" @@ -72,25 +73,15 @@ var migratePamCmd = &cobra.Command{ // TODO: assign error and check legacyClient, _ := initClient(false) - log.Info().Msg("looking up usage of <> PAM Provider") + found, fromPamProvider, processedError := getExistingPamProvider(sdkClient, migrateFrom) - log.Debug().Msg("call: PAMProviderGetPamProviders()") - listPamProvidersInUse, httpResponse, rErr := sdkClient.PAMProviderApi.PAMProviderGetPamProviders(context.Background()). - XKeyfactorRequestedWith(XKeyfactorRequestedWith).XKeyfactorApiVersion(XKeyfactorApiVersion). - PqQueryString(fmt.Sprintf("name -eq \"%s\"", migrateFrom)). - Execute() - log.Debug().Msg("returned: PAMProviderGetPamProviders()") - - if rErr != nil { - log.Error().Err(rErr).Send() - return returnHttpErr(httpResponse, rErr) + if processedError != nil { + return processedError } - jobject, _ := json.MarshalIndent(listPamProvidersInUse, "", " ") - fmt.Println(string(jobject)) - - // TODO: ensure only 1 returned PAM Provider definition - fromPamProvider := listPamProvidersInUse[0] + if found == false { + return errors.New("Original PAM Provider to migrate from was not found by Name") + } // get PAM Type definition for PAM Provider migrating <> log.Debug().Msg("call: PAMProviderGetPamProviders()") @@ -151,14 +142,14 @@ var migratePamCmd = &cobra.Command{ } } - jobject, _ = json.MarshalIndent(inUsePamParamValues, "", " ") + jobject, _ := json.MarshalIndent(inUsePamParamValues, "", " ") fmt.Println(string(jobject)) // step through list of every defined param value // record unique GUIDs of every param value on InstanceLevel : true // InstanceLevel : true is for per-secret fields // InstanceLevel : false is provider level secrets - these are also recorded for migration - for _, pamParam := range listPamProvidersInUse[0].ProviderTypeParamValues { + for _, pamParam := range fromPamProvider.ProviderTypeParamValues { // jobject, _ = json.MarshalIndent(pamParam, "", " ") // fmt.Println(string(jobject)) fieldName := *pamParam.ProviderTypeParam.Name @@ -182,87 +173,24 @@ var migratePamCmd = &cobra.Command{ // mark GUID ID for pam type // mark integer IDs for each Parameter type - // TODO: check that migration target PAM Provider was not already created + var migrationTargetPamProvider keyfactor.CSSCMSDataModelModelsProvider - fmt.Println("creating new Provider of migration target PAM Type") - var migrationPamProvider keyfactor.CSSCMSDataModelModelsProvider - migrationPamProvider.Name = fromPamProvider.Name + appendName - migrationPamProvider.ProviderType = keyfactor.CSSCMSDataModelModelsProviderType{ - Id: toPamType.Id, - } - var onevalue int32 = 1 - migrationPamProvider.Area = &onevalue - migrationPamProvider.SecuredAreaId = nil + // check if target PAM Provider already exists + found, migrationTargetPamProvider, processedError = getExistingPamProvider(sdkClient, fromPamProvider.Name+appendName) - // need to init AdditionalProperties map when setting value - migrationPamProvider.AdditionalProperties = map[string]interface{}{ - "Remote": false, // this property is not on the model for some reason + if processedError != nil { + return processedError } - fmt.Println("getting migration target PAM Type parameter definitions, InstanceLevel : false") - // TODO: check typing, have to access "Parameters" instead of ProviderTypeParams - for _, pamParamType := range fromPamType.AdditionalProperties["Parameters"].([]interface{}) { - if !pamParamType.(map[string]interface{})["InstanceLevel"].(bool) { - // found a provider level parameter - // need to find the value to map over - // then create an object with that value and TypeParam settings - paramName := pamParamType.(map[string]interface{})["Name"].(string) - paramValue := selectProviderParamValue(paramName, fromPamProvider.ProviderTypeParamValues) - paramTypeId := selectProviderTypeParamId(paramName, toPamType.AdditionalProperties["Parameters"].([]interface{})) - falsevalue := false - providerLevelParameter := keyfactor.CSSCMSDataModelModelsPamProviderTypeParamValue{ - Value: ¶mValue, - ProviderTypeParam: &keyfactor.CSSCMSDataModelModelsProviderTypeParam{ - Id: ¶mTypeId, - Name: ¶mName, - InstanceLevel: &falsevalue, - }, - } - - // append filled out provider type parameter object, which contains the Provider-level parameter values - // migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) + // create PAM Provider if it does not exist already + if found == false { + migrationTargetPamProvider, processedError = createMigrationTargetPamProvider(sdkClient, fromPamProvider, fromPamType, toPamType, appendName) - // TODO: need to explicit filter for CyberArk expected params, i.e. not map over Safe - // this needs to be done programatically for other provider types - if paramName == "AppId" { - migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) - } + if processedError != nil { + return processedError } } - msg := "Created new PAM Provider definition to be created." - fmt.Println(msg) - log.Info().Msg(msg) - jobject, _ = json.MarshalIndent(migrationPamProvider, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ PAM Provider to be created ^^^") - - // POST new PAM Provider - // create new PAM Instance of designated <> type - // set area = 1 or previous value - // name = old name plus append parameter - // providertype.id = Type GUID - // for all provider level values: - // set value to migrating value - // null for instanceid, instanceguid - // providertypeparam should be set to all matching values from GET TYPES - // ignoring datatype - - createdPamProvider, httpResponse, rErr := sdkClient.PAMProviderApi.PAMProviderCreatePamProvider(context.Background()). - Provider(migrationPamProvider). - XKeyfactorRequestedWith(XKeyfactorRequestedWith).XKeyfactorApiVersion(XKeyfactorApiVersion). - Execute() - - if rErr != nil { - log.Error().Err(rErr).Send() - return returnHttpErr(httpResponse, rErr) - } - - fmt.Println("vvv CREATED MIGRATION PAM PROVIDER vvv") - jobject, _ = json.MarshalIndent(createdPamProvider, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ CREATED MIGRATION PAM PROVIDER ^^^") - // foreach store GUID pass in as a parameter----- // GET Store by GUID (instance GUID matches Store Id GUID) // output some store info to confirm @@ -300,7 +228,7 @@ var migratePamCmd = &cobra.Command{ // check if Pam Secret is using our migrating provider if *fromPamProvider.Id == int32(propSecret["ProviderId"].(float64)) { // Pam Secret that Needs to be migrated - formattedSecret["Value"] = buildMigratedPamSecret(propSecret, fromProviderLevelParamValues, *createdPamProvider.Id) + formattedSecret["Value"] = buildMigratedPamSecret(propSecret, fromProviderLevelParamValues, *migrationTargetPamProvider.Id) } else { // reformat to required POST format for properties formattedSecret["Value"] = reformatPamSecretForPost(propSecret) @@ -354,6 +282,119 @@ var migratePamCmd = &cobra.Command{ }, } +func getExistingPamProvider(sdkClient *keyfactor.APIClient, name string) (bool, keyfactor.CSSCMSDataModelModelsProvider, error) { + var pamProvider keyfactor.CSSCMSDataModelModelsProvider + + logMsg := fmt.Sprintf("Looking up usage of PAM Provider with name %s", name) + log.Debug().Msg(logMsg) + fmt.Println(logMsg) + + foundProvider, httpResponse, err := sdkClient.PAMProviderApi.PAMProviderGetPamProviders(context.Background()). + XKeyfactorRequestedWith(XKeyfactorRequestedWith).XKeyfactorApiVersion(XKeyfactorApiVersion). + PqQueryString(fmt.Sprintf("name -eq \"%s\"", name)). + Execute() + + logMsg = fmt.Sprintf("Reading response for PAM Provider with name %s", name) + log.Debug().Msg(logMsg) + fmt.Println(logMsg) + + if err != nil { + log.Error().Err(err).Send() + return false, pamProvider, returnHttpErr(httpResponse, err) + } + + if len(foundProvider) != 1 { + logMsg = "More than one PAM Provider returned for the same name. This is not supported behavior." + log.Error().Msg(logMsg) + return false, pamProvider, errors.New(logMsg) + } + + return true, foundProvider[0], nil +} + +func createMigrationTargetPamProvider(sdkClient *keyfactor.APIClient, fromPamProvider keyfactor.CSSCMSDataModelModelsProvider, fromPamType keyfactor.CSSCMSDataModelModelsProviderType, toPamType keyfactor.CSSCMSDataModelModelsProviderType, appendName string) (keyfactor.CSSCMSDataModelModelsProvider, error) { + fmt.Println("creating new Provider of migration target PAM Type") + var migrationPamProvider keyfactor.CSSCMSDataModelModelsProvider + migrationPamProvider.Name = fromPamProvider.Name + appendName + migrationPamProvider.ProviderType = keyfactor.CSSCMSDataModelModelsProviderType{ + Id: toPamType.Id, + } + var onevalue int32 = 1 + migrationPamProvider.Area = &onevalue + migrationPamProvider.SecuredAreaId = nil + + // need to init AdditionalProperties map when setting value + migrationPamProvider.AdditionalProperties = map[string]interface{}{ + "Remote": false, // this property is not on the model for some reason + } + + fmt.Println("getting migration target PAM Type parameter definitions, InstanceLevel : false") + // TODO: check typing, have to access "Parameters" instead of ProviderTypeParams + for _, pamParamType := range fromPamType.AdditionalProperties["Parameters"].([]interface{}) { + if !pamParamType.(map[string]interface{})["InstanceLevel"].(bool) { + // found a provider level parameter + // need to find the value to map over + // then create an object with that value and TypeParam settings + paramName := pamParamType.(map[string]interface{})["Name"].(string) + paramValue := selectProviderParamValue(paramName, fromPamProvider.ProviderTypeParamValues) + paramTypeId := selectProviderTypeParamId(paramName, toPamType.AdditionalProperties["Parameters"].([]interface{})) + falsevalue := false + providerLevelParameter := keyfactor.CSSCMSDataModelModelsPamProviderTypeParamValue{ + Value: ¶mValue, + ProviderTypeParam: &keyfactor.CSSCMSDataModelModelsProviderTypeParam{ + Id: ¶mTypeId, + Name: ¶mName, + InstanceLevel: &falsevalue, + }, + } + + // append filled out provider type parameter object, which contains the Provider-level parameter values + // migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) + + // TODO: need to explicit filter for CyberArk expected params, i.e. not map over Safe + // this needs to be done programatically for other provider types + if paramName == "AppId" { + migrationPamProvider.ProviderTypeParamValues = append(migrationPamProvider.ProviderTypeParamValues, providerLevelParameter) + } + } + } + + msg := "Created new PAM Provider definition to be created." + fmt.Println(msg) + log.Info().Msg(msg) + jobject, _ := json.MarshalIndent(migrationPamProvider, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ PAM Provider to be created ^^^") + + // POST new PAM Provider + // create new PAM Instance of designated <> type + // set area = 1 or previous value + // name = old name plus append parameter + // providertype.id = Type GUID + // for all provider level values: + // set value to migrating value + // null for instanceid, instanceguid + // providertypeparam should be set to all matching values from GET TYPES + // ignoring datatype + + createdPamProvider, httpResponse, rErr := sdkClient.PAMProviderApi.PAMProviderCreatePamProvider(context.Background()). + Provider(migrationPamProvider). + XKeyfactorRequestedWith(XKeyfactorRequestedWith).XKeyfactorApiVersion(XKeyfactorApiVersion). + Execute() + + if rErr != nil { + log.Error().Err(rErr).Send() + return *createdPamProvider, returnHttpErr(httpResponse, rErr) + } + + fmt.Println("vvv CREATED MIGRATION PAM PROVIDER vvv") + jobject, _ = json.MarshalIndent(createdPamProvider, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ CREATED MIGRATION PAM PROVIDER ^^^") + + return *createdPamProvider, nil +} + func selectProviderParamValue(name string, providerParameters []keyfactor.CSSCMSDataModelModelsPamProviderTypeParamValue) string { for _, parameter := range providerParameters { if name == *parameter.ProviderTypeParam.Name { From 3a53e44c91a11a97462e3402b7f0b3cde168bdc2 Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:41:19 +0000 Subject: [PATCH 7/9] feat(migrate): add check command for finding active pam usage --- cmd/migrate.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index 7d8de88..9a38096 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -19,6 +19,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" // "github.com/Keyfactor/keyfactor-go-client-sdk/v24/api/keyfactor/v2" "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/keyfactor" @@ -35,6 +36,99 @@ var migrateCmd = &cobra.Command{ to new Extension implementations that have definitions that differ from prior releases.`, } +var migrateCheckCmd = &cobra.Command{ + Use: "check", + Short: "Check usage of a feature to migrate. Currently only PAM is supported.", + Long: "Check usage of a feature to migrate. Currently only PAM is supported", + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + isExperimental := true + + // load specified flags + fromCheck, _ := cmd.Flags().GetString("from") // name of entity, e.g. PAM Provider + pamCheck, _ := cmd.Flags().GetBool("pam-usage") + + if pamCheck == false { + return errors.New("Flag --pam-usage was not specified, but this is the only currently supported use case.") + } + + // Debug + expEnabled checks + informDebug(debugFlag) + debugErr := warnExperimentalFeature(expEnabled, isExperimental) + if debugErr != nil { + return debugErr + } + + // Log flags + log.Info().Str("from", fromCheck). + Bool("pam-usage", pamCheck). + Msg("migrate PAM Provider") + + sdkClient, err := initGenClient(false) + if err != nil { + return err + } + + // get all secret GUIDs for PAM Provider + found, pamProvider, err := getExistingPamProvider(sdkClient, fromCheck) + + activePamSecretGuids := map[string]bool{} + for _, param := range pamProvider.ProviderTypeParamValues { + if param.InstanceGuid != nil { + // enter every instance guid as a key with value true + // represents an active Secret being managed in this pam provider + // the same key will be set multiple times for each parameter for a particular Secret, but this should be no issue + activePamSecretGuids[*param.InstanceGuid] = true + } + } + + if err != nil { + log.Error().Err(err).Send() + return err + } + + if found == false { + return errors.New("Named entity in 'from' argument was not found, no check can be run.") + } + + legacyClient, err := initClient(false) + if err != nil { + return err + } + + // get all certificate stores + certStoreList, err := legacyClient.ListCertificateStores(nil) + + if err != nil { + log.Error().Err(err).Send() + return err + } + + certStoreGuids := map[string]bool{} + // loop through every found certificate store + for _, store := range *certStoreList { + // get properties field, as this will contain the Secret GUID for one of our active Instances if the PAM provider is in use + storeProperties := store.PropertiesString + + // loop through all found Instance GUIDs of the PAM Provider + // if the GUID is present in the Properties field, add this Store ID to the list to return + for instanceGuid, _ := range activePamSecretGuids { + if strings.Contains(storeProperties, instanceGuid) { + certStoreGuids[store.Id] = true + } + } + } + + // print out list of Cert Store GUIDs + fmt.Println("\nThe following Cert Store Ids are using the PAM Provider with name '" + fromCheck + "'\n") + for storeId, _ := range certStoreGuids { + fmt.Println(storeId) + } + + return nil + }, +} + var migratePamCmd = &cobra.Command{ Use: "pam", Short: "Migrate existing PAM Provider usage to a new PAM Provider", @@ -303,12 +397,18 @@ func getExistingPamProvider(sdkClient *keyfactor.APIClient, name string) (bool, return false, pamProvider, returnHttpErr(httpResponse, err) } - if len(foundProvider) != 1 { + if len(foundProvider) > 1 { logMsg = "More than one PAM Provider returned for the same name. This is not supported behavior." log.Error().Msg(logMsg) return false, pamProvider, errors.New(logMsg) } + if len(foundProvider) == 0 { + logMsg = "No PAM Provider was found with the given name." + log.Warn().Msg(logMsg) + return false, pamProvider, nil + } + return true, foundProvider[0], nil } @@ -465,13 +565,37 @@ func buildMigratedPamSecret(secretProp map[string]interface{}, fromProviderLevel } func init() { + RootCmd.AddCommand(migrateCmd) + + // migrate check + var pamCheck bool + var fromCheck string + + migrateCmd.AddCommand(migrateCheckCmd) + + migrateCheckCmd.Flags().BoolVar( + &pamCheck, + "pam-usage", + true, + "Specify this flag to check usage of a PAM Provider named with the 'from' argument. Returns a list of Certificate Store GUIDs using that provider.", + ) + + migrateCheckCmd.Flags().StringVarP( + &fromCheck, + "from", + "f", + "", + "The name of the KF entity to search for usage of. Behavior will be different depending on type of check specified.", + ) + + migrateCheckCmd.MarkFlagRequired("from") + + // migrate pam var from string var to string var appendName string var store string - RootCmd.AddCommand(migrateCmd) - migrateCmd.AddCommand(migratePamCmd) migratePamCmd.Flags().StringVarP( From 3d9bf7a448f8070623e4182a7589b7dca6cfbfcf Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Wed, 9 Jul 2025 22:01:02 +0000 Subject: [PATCH 8/9] fix(migrate): use debug flag to show verbose object logging --- cmd/migrate.go | 108 ++++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 47 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index 9a38096..14fb33e 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -203,41 +203,46 @@ var migratePamCmd = &cobra.Command{ for _, pamType := range pamTypes { if *pamType.Id == *fromPamProvider.ProviderType.Id { fromPamType = pamType - // TODO: remove debugging - fmt.Println("vvv FROM TYPE vvv") - jobject, _ := json.MarshalIndent(pamType, "", " ") - fmt.Println(string(jobject)) - jobject, _ = json.MarshalIndent(pamType.AdditionalProperties["Parameters"], "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ FROM TYPE ^^^") + + if debugFlag { + fmt.Println("vvv FROM TYPE vvv") + jobject, _ := json.MarshalIndent(pamType, "", " ") + fmt.Println(string(jobject)) + jobject, _ = json.MarshalIndent(pamType.AdditionalProperties["Parameters"], "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ FROM TYPE ^^^") + } } if *pamType.Name == migrateTo { toPamType = pamType - // TODO: remove debugging - fmt.Println("vvv TO TYPE vvv") - jobject, _ := json.MarshalIndent(pamType, "", " ") - fmt.Println(string(jobject)) - jobject, _ = json.MarshalIndent(pamType.AdditionalProperties["Parameters"], "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ TO TYPE ^^^") + + if debugFlag { + fmt.Println("vvv TO TYPE vvv") + jobject, _ := json.MarshalIndent(pamType, "", " ") + fmt.Println(string(jobject)) + jobject, _ = json.MarshalIndent(pamType.AdditionalProperties["Parameters"], "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ TO TYPE ^^^") + } } } // TODO: check typing, have to access "Parameters" instead of ProviderTypeParams for _, pamParamType := range fromPamType.AdditionalProperties["Parameters"].([]interface{}) { - jobject, _ := json.MarshalIndent(pamParamType, "", " ") - fmt.Println(string(jobject)) if pamParamType.(map[string]interface{})["InstanceLevel"].(bool) { // found definition of an instance level param for the type in question // create key in map for the field name inUsePamParamValues[pamParamType.(map[string]interface{})["Name"].(string)] = map[string]string{} - fmt.Println("made it!") } } - jobject, _ := json.MarshalIndent(inUsePamParamValues, "", " ") - fmt.Println(string(jobject)) + if debugFlag { + fmt.Println("vvv IN USE PAM PROVIDER PARAMETER INSTANCES vvv") + jobject, _ := json.MarshalIndent(inUsePamParamValues, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ IN USE PAM PROVIDER PARAMETER INSTANCES ^^^") + } // step through list of every defined param value // record unique GUIDs of every param value on InstanceLevel : true @@ -254,13 +259,13 @@ var migratePamCmd = &cobra.Command{ fromProviderLevelParamValues[fieldName] = *pamParam.Value } } - jobject, _ = json.MarshalIndent(inUsePamParamValues, "", " ") - fmt.Println(string(jobject)) - - // TODO: make sure every field has the same number of GUIDs tracked - // tally GUID count for logging - // log.Info().Msgf("Found %d PAM Provider usages of Provider %s",) + if debugFlag { + fmt.Println("vvv IN USE PAM PROVIDER PARAMETER VALUES vvv") + jobject, _ := json.MarshalIndent(inUsePamParamValues, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ IN USE PAM PROVIDER PARAMETER VALUES ^^^") + } // GET all PAM Types // select array entry with matching Name field of <> @@ -296,13 +301,17 @@ var migratePamCmd = &cobra.Command{ return rErr } - jobject, _ = json.MarshalIndent(certStore, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ found cert store ^^^") + if debugFlag { + fmt.Println("vvv FOUND CERT STORE vvv") + jobject, _ := json.MarshalIndent(certStore, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ FOUND CERT STORE ^^^") - jobject, _ = json.MarshalIndent(certStore.Properties, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ cert store properties ^^^") + fmt.Println("vvv CERT STORE PROPERTIES vvv") + jobject, _ = json.MarshalIndent(certStore.Properties, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ CERT STORE PROPERTIES ^^^") + } // foreach property key (properties is an object not an array) // if value is an object, and object has an InstanceGuid @@ -340,14 +349,12 @@ var migratePamCmd = &cobra.Command{ } } - jobject, _ = json.MarshalIndent(certStore.Properties, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ SECRETS REFORMATTED ^^^") - - // propertiesAsString, _ := json.Marshal(certStore.Properties) - // jsonProps := string(propertiesAsString) - // escapedProps := strings.ReplaceAll(jsonProps, "\"", "\\\"") - // fmt.Println(escapedProps) + if debugFlag { + fmt.Println("vvv SECRETS REFORMATTED vvv") + jobject, _ := json.MarshalIndent(certStore.Properties, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ SECRETS REFORMATTED ^^^") + } // update property object // set required fields, and new Properties @@ -368,7 +375,8 @@ var migratePamCmd = &cobra.Command{ return rErr } - jobject, _ = json.MarshalIndent(updatedStore, "", " ") + fmt.Println("vvv UPDATED STORE vvv") + jobject, _ := json.MarshalIndent(updatedStore, "", " ") fmt.Println(string(jobject)) fmt.Println("^^^ UPDATED STORE ^^^") @@ -462,9 +470,13 @@ func createMigrationTargetPamProvider(sdkClient *keyfactor.APIClient, fromPamPro msg := "Created new PAM Provider definition to be created." fmt.Println(msg) log.Info().Msg(msg) - jobject, _ := json.MarshalIndent(migrationPamProvider, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ PAM Provider to be created ^^^") + + if debugFlag { + fmt.Println("vvv PAM PROVIDER TO BE CREATED vvv") + jobject, _ := json.MarshalIndent(migrationPamProvider, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ PAM PROVIDER TO BE CREATED ^^^") + } // POST new PAM Provider // create new PAM Instance of designated <> type @@ -487,10 +499,12 @@ func createMigrationTargetPamProvider(sdkClient *keyfactor.APIClient, fromPamPro return *createdPamProvider, returnHttpErr(httpResponse, rErr) } - fmt.Println("vvv CREATED MIGRATION PAM PROVIDER vvv") - jobject, _ = json.MarshalIndent(createdPamProvider, "", " ") - fmt.Println(string(jobject)) - fmt.Println("^^^ CREATED MIGRATION PAM PROVIDER ^^^") + if debugFlag { + fmt.Println("vvv CREATED MIGRATION PAM PROVIDER vvv") + jobject, _ := json.MarshalIndent(createdPamProvider, "", " ") + fmt.Println(string(jobject)) + fmt.Println("^^^ CREATED MIGRATION PAM PROVIDER ^^^") + } return *createdPamProvider, nil } From 278c560f30958eae3fede1865934a879f34658b4 Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Wed, 9 Jul 2025 22:17:19 +0000 Subject: [PATCH 9/9] chore(docs): update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8c668..ee9b56e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# v1.8.0 + +## Features + +### CLI + +- `migrate check --pam-usage`: Looks up usage of a named PAM Provider, and returns the store IDs of all stores using the Provider. Used in conjunction with the `migrate pam` command. +- `migrate pam`: Will migrate legacy CyberArk PAM Provider usage (named "CyberArk") to the "CyberArk-SdkCredentialProvider" type when prompted with a Store ID for a certificate store using a "CyberArk" provider + # v1.7.0 ## Features