From 156730277ae1cf69dd4cefc1c0244e198dc739ea Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:21:35 -0800 Subject: [PATCH 01/29] chore(deps): Bump `keyfactor-auth-client-go` to `v1.1.2` --- v3/go.mod | 2 +- v3/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v3/go.mod b/v3/go.mod index 9fa544d..372fe63 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -19,7 +19,7 @@ go 1.23 toolchain go1.23.2 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.1.1-rc.0 + github.com/Keyfactor/keyfactor-auth-client-go v1.1.2 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/spbsoluble/go-pkcs12 v0.3.3 go.mozilla.org/pkcs7 v0.9.0 diff --git a/v3/go.sum b/v3/go.sum index 67d7ee9..dd025be 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.1.1-rc.0 h1:/N/7pBj/oTUM1cYga2NvKyA4q6nfE0acciJHZqKC9Ug= -github.com/Keyfactor/keyfactor-auth-client-go v1.1.1-rc.0/go.mod h1:yw92P9gSYVEyWkiUAJFsb7hjhXa8slN1+yTQgjSgovM= +github.com/Keyfactor/keyfactor-auth-client-go v1.1.2 h1:o/BCmICXUErZcodteat3/jrMjzkh8hlNI5kzXB+wsSw= +github.com/Keyfactor/keyfactor-auth-client-go v1.1.2/go.mod h1:yw92P9gSYVEyWkiUAJFsb7hjhXa8slN1+yTQgjSgovM= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 278bfdcea981a0e2c0ebee409f9a5be51a04a429 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:23:46 -0800 Subject: [PATCH 02/29] chore(deps): ``` go: upgraded github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 => v1.17.0 go: upgraded github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 => v1.8.1 go: upgraded github.com/fatih/color v1.13.0 => v1.18.0 go: upgraded github.com/hashicorp/go-hclog v1.5.0 => v1.6.3 go: upgraded github.com/mattn/go-colorable v0.1.13 => v0.1.14 go: upgraded github.com/mattn/go-isatty v0.0.19 => v0.0.20 go: upgraded golang.org/x/crypto v0.30.0 => v0.32.0 go: upgraded golang.org/x/net v0.32.0 => v0.34.0 go: upgraded golang.org/x/oauth2 v0.24.0 => v0.25.0 go: upgraded golang.org/x/sys v0.28.0 => v0.29.0 ``` --- v3/go.mod | 20 ++++++++++---------- v3/go.sum | 49 ++++++++++++++++++++++++------------------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/v3/go.mod b/v3/go.mod index 372fe63..9dcc91a 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -26,24 +26,24 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/v3/go.sum b/v3/go.sum index dd025be..c4f0ef2 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -1,9 +1,9 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0= @@ -23,16 +23,17 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= @@ -45,21 +46,20 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= -github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spbsoluble/go-pkcs12 v0.3.3 h1:3nh7IKn16RDpmrSMtOu1JvbB0XHYq1j+IsICdU1c7J4= @@ -70,22 +70,21 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 0bac539dde14e76ed788c3c587abed57e14103cd Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:28:05 -0800 Subject: [PATCH 03/29] chore(deps): Bump `github.com/Keyfactor/keyfactor-auth-client-go` to `v1.2.0-rc.0` --- v3/go.mod | 2 +- v3/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v3/go.mod b/v3/go.mod index 9dcc91a..9e4c372 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -19,7 +19,7 @@ go 1.23 toolchain go1.23.2 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.1.2 + github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/spbsoluble/go-pkcs12 v0.3.3 go.mozilla.org/pkcs7 v0.9.0 diff --git a/v3/go.sum b/v3/go.sum index c4f0ef2..49df6d3 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.1.2 h1:o/BCmICXUErZcodteat3/jrMjzkh8hlNI5kzXB+wsSw= -github.com/Keyfactor/keyfactor-auth-client-go v1.1.2/go.mod h1:yw92P9gSYVEyWkiUAJFsb7hjhXa8slN1+yTQgjSgovM= +github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.0 h1:mm5NpyEJhm4em9nn8gWNFtsk211F3IY4oAH8kfZSAqs= +github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.0/go.mod h1:8/msKJyHU7+GwW3n9aYQIyW7Lu5/njaOsFL9dZoQ+Ek= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From d897eea8aa53cafb76d2df380718b2e99b67b4e2 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:15:44 -0800 Subject: [PATCH 04/29] chore(deps): Bump `github.com/Keyfactor/keyfactor-auth-client-go` to `v1.2.0-rc.5` --- v3/go.mod | 2 +- v3/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v3/go.mod b/v3/go.mod index 9e4c372..6a0ed78 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -19,7 +19,7 @@ go 1.23 toolchain go1.23.2 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.0 + github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/spbsoluble/go-pkcs12 v0.3.3 go.mozilla.org/pkcs7 v0.9.0 diff --git a/v3/go.sum b/v3/go.sum index 49df6d3..5656f2b 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.0 h1:mm5NpyEJhm4em9nn8gWNFtsk211F3IY4oAH8kfZSAqs= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.0/go.mod h1:8/msKJyHU7+GwW3n9aYQIyW7Lu5/njaOsFL9dZoQ+Ek= +github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5 h1:3BpabiIcvdSszX7cx7OLBlpS8c7CnmvdquAANOD5+2o= +github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5/go.mod h1:7htRcBIWn+X4fI5jaYBALSYwP84H/djN7d8y3n0ZDQ0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From f50e0a70fab058495ec422268eafb77f8de9357c Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Mon, 3 Mar 2025 12:51:24 -0800 Subject: [PATCH 05/29] fix(certs): When creating subject escape any values that main contain commas. feat(certs): Add `collectionId` support for certificate downloads. --- v3/api/certificate.go | 36 ++++++++++++++++++++++++++++++------ v3/go.mod | 2 +- v3/go.sum | 4 ++-- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/v3/api/certificate.go b/v3/api/certificate.go index c734c65..95a7af8 100644 --- a/v3/api/certificate.go +++ b/v3/api/certificate.go @@ -196,6 +196,7 @@ func (c *Client) DownloadCertificate( thumbprint string, serialNumber string, issuerDn string, + collectionId int, ) (*x509.Certificate, []*x509.Certificate, error) { log.Println("[INFO] Downloading certificate") @@ -228,6 +229,19 @@ func (c *Client) DownloadCertificate( ChainOrder: "EndEntityFirst", } + query := apiQuery{ + Query: []StringTuple{}, + } + if collectionId > 0 { + log.Println("[DEBUG] RecoverCertificate: Collection ID:", collectionId) + query.Query = append( + query.Query, StringTuple{ + "collectionId", fmt.Sprintf("%d", collectionId), + }, + ) + log.Println("[DEBUG] RecoverCertificate: Query:", query) + } + // Set Keyfactor-specific headers headers := &apiHeaders{ Headers: []StringTuple{ @@ -242,6 +256,7 @@ func (c *Client) DownloadCertificate( Endpoint: "Certificates/Download", Headers: headers, Payload: payload, + Query: &query, } resp, err := c.sendRequest(keyfactorAPIStruct) @@ -754,30 +769,39 @@ func createSubject(cs CertificateSubject) (string, error) { var subject string if cs.SubjectCommonName != "" && cs.SubjectCommonName != "" { - subject = "CN=" + cs.SubjectCommonName + "," + subject = "CN=" + escapeDNValue(cs.SubjectCommonName) + "," } else { return "", errors.New("build subject: common name required") // Common name is required! } if cs.SubjectOrganizationalUnit != "" && cs.SubjectOrganizationalUnit != "" { - subject += "OU=" + cs.SubjectOrganizationalUnit + "," + subject += "OU=" + escapeDNValue(cs.SubjectOrganizationalUnit) + "," } if cs.SubjectOrganization != "" && cs.SubjectOrganization != "" { - subject += "O=" + cs.SubjectOrganization + "," + subject += "O=" + escapeDNValue(cs.SubjectOrganization) + "," } if cs.SubjectLocality != "" && cs.SubjectLocality != "" { - subject += "L=" + cs.SubjectLocality + "," + subject += "L=" + escapeDNValue(cs.SubjectLocality) + "," } if cs.SubjectState != "" && cs.SubjectState != "" { - subject += "ST=" + cs.SubjectState + "," + subject += "ST=" + escapeDNValue(cs.SubjectState) + "," } if cs.SubjectCountry != "" && cs.SubjectCountry != "" { - subject += "C=" + cs.SubjectCountry + "," + subject += "C=" + escapeDNValue(cs.SubjectCountry) + "," } subject = strings.TrimRight(subject, ",") // remove trailing comma log.Printf("[DEBUG] createSubject(): Certificate subject created: %s\n", subject) return subject, nil } +// escapeDNValue ensures that a value in a DN is properly escaped if it contains special characters. +func escapeDNValue(value string) string { + // If the value contains a comma, quote it + if strings.Contains(value, ",") { + return `"` + value + `"` + } + return value +} + // validateDeployPFXArgs validates the arguments required to deploy a PFX certificate. func validateDeployPFXArgs(dpfxa *DeployPFXArgs) error { if dpfxa.StoreIds == nil { diff --git a/v3/go.mod b/v3/go.mod index 6a0ed78..a2b12ff 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -19,7 +19,7 @@ go 1.23 toolchain go1.23.2 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5 + github.com/Keyfactor/keyfactor-auth-client-go v1.2.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/spbsoluble/go-pkcs12 v0.3.3 go.mozilla.org/pkcs7 v0.9.0 diff --git a/v3/go.sum b/v3/go.sum index 5656f2b..2b13c8a 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5 h1:3BpabiIcvdSszX7cx7OLBlpS8c7CnmvdquAANOD5+2o= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5/go.mod h1:7htRcBIWn+X4fI5jaYBALSYwP84H/djN7d8y3n0ZDQ0= +github.com/Keyfactor/keyfactor-auth-client-go v1.2.0 h1:uNSlyOW5Bqpi0nsOGZtOYQzN0vP/h4S4J38jtQes+OI= +github.com/Keyfactor/keyfactor-auth-client-go v1.2.0/go.mod h1:7htRcBIWn+X4fI5jaYBALSYwP84H/djN7d8y3n0ZDQ0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From a6dff6fd81b29310dd8c671450a38bda37ff58ad Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Sun, 21 Sep 2025 16:12:17 -0500 Subject: [PATCH 06/29] Expand secret type (#47) --- v3/api/store_models.go | 83 +++++++++++++++++++++++++++++------------- v3/go.mod | 30 +++++++-------- v3/go.sum | 64 ++++++++++++++++---------------- 3 files changed, 104 insertions(+), 73 deletions(-) diff --git a/v3/api/store_models.go b/v3/api/store_models.go index 5938e6a..bf4db2c 100644 --- a/v3/api/store_models.go +++ b/v3/api/store_models.go @@ -51,14 +51,20 @@ type UpdateStoreFctArgs struct { // automatically populated by the CreateStore method. However, if configured, this field will be used. PropertiesString string `json:"Properties,omitempty"` // Mapped name-value pair field used to configure properties. - Properties map[string]interface{} `json:"-"` - AgentId string `json:"AgentId"` - AgentAssigned *bool `json:"AgentAssigned,omitempty"` - ContainerName *string `json:"ContainerName,omitempty"` - InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` - ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` - SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` - Password *StorePasswordConfig `json:"Password"` + Properties map[string]interface{} `json:"-"` + AgentId string `json:"AgentId"` + AgentAssigned *bool `json:"AgentAssigned,omitempty"` + ContainerName *string `json:"ContainerName,omitempty"` + InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` + ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` + SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` + Password *UpdateStorePasswordConfig `json:"Password"` +} + +type UpdateStorePasswordConfig struct { + SecretValue *string `json:"SecretValue"` // used for setting kf-secret value or No Value (null) + Parameters map[string]string `json:"Parameters"` + Provider int `json:"Provider"` } // InventorySchedule holds configuration data for creating an inventory schedule for a certificate store in Keyfactor @@ -94,34 +100,59 @@ type ReEnrollmnentConfig struct { } // StorePasswordConfig configures the password field for a new certificate store. +// TODO: make re-usable struct for Secret type fields type StorePasswordConfig struct { - Value *string `json:"SecretValue"` - SecretTypeGuid *string `json:"SecretTypeGuid,omitempty"` - InstanceId *string `json:"InstanceId,omitempty"` + Value *string `json:"SecretValue"` + SecretTypeGuid *string `json:"SecretTypeGuid,omitempty"` + InstanceId *string `json:"InstanceId,omitempty"` + InstanceGuid *string `json:"InstanceGuid,omitempty"` + ProvidererTypeParameterValues *[]ProviderTypeParameterValue `json:"ProviderTypeParameterValues"` + ProviderId int `json:"ProviderId"` + IsManaged bool `json:"IsManaged"` + HasValue bool `json:"HasValue"` } // ProviderTypeParameterValues - Not yet implemented // ProviderTypeParameterValues ProviderTypeParams - Not implemented /* Future non-critical functionality */ -type ProviderTypeParams struct { - Id string - Value string - InstanceId string - InstanceGuid string - Provider ProviderParams +type ProviderTypeParameterValue struct { + Id int `json:"Id"` + Value *string `json:"Value"` + ParameterId int `json:"ParameterId"` // defaults always to 0, likely deprecated + InstanceId *string `json:"InstanceId"` // defaults null, likely deprecated + InstanceGuid *string `json:"InstanceGuid"` + Provider *string `json:"Provider"` // defaults null, likely deprecated + ProviderTypeParam ProviderTypeParam `json:"ProviderTypeParam"` } -type ProviderParams struct { - Id int - Name string - Area int - ProviderType ProviderType +type ProviderTypeParam struct { + Id int `json:"Id"` + Name *string `json:"Name"` + DisplayName *string `json:"DisplayName"` + DataType int `json:"DataType"` + InstanceLevel bool `json:"InstanceLevel"` + ProviderType *string `json:"ProviderType"` //defaults null, likely deprecated } -type ProviderType struct { - Id string - Name string -} +// type ProviderTypeParams struct { +// Id string +// Value string +// InstanceId string +// InstanceGuid string +// Provider ProviderParams +// } + +// type ProviderParams struct { +// Id int +// Name string +// Area int +// ProviderType ProviderType +// } + +// type ProviderType struct { +// Id string +// Name string +// } // CertStoreTypeResponse contains the response elements returned from the GetCertificateStoreType method. type CertStoreTypeResponse struct { diff --git a/v3/go.mod b/v3/go.mod index 6a0ed78..d69aa62 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -14,36 +14,36 @@ module github.com/Keyfactor/keyfactor-go-client/v3 -go 1.23 +go 1.24 -toolchain go1.23.2 +toolchain go1.24.5 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5 + github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/spbsoluble/go-pkcs12 v0.3.3 go.mozilla.org/pkcs7 v0.9.0 ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/fatih/color v1.18.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/v3/go.sum b/v3/go.sum index 5656f2b..2008907 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -1,21 +1,21 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0/go.mod h1:hd8hTTIY3VmUVPRHNH7GVCHO3SHgXkJKZHReby/bnUQ= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 h1:mrkDCdkMsD4l9wjFGhofFHFrV43Y3c53RSLKOCJ5+Ow= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5 h1:3BpabiIcvdSszX7cx7OLBlpS8c7CnmvdquAANOD5+2o= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.5/go.mod h1:7htRcBIWn+X4fI5jaYBALSYwP84H/djN7d8y3n0ZDQ0= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 h1:otC213b6CYzqeN9b3CRlH1Qj1hTFIN5nqPA8gTlHdLg= +github.com/Keyfactor/keyfactor-auth-client-go v1.3.0/go.mod h1:97vCisBNkdCK0l2TuvOSdjlpvQa4+GHsMut1UTyv1jo= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -26,8 +26,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -36,8 +36,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -58,8 +58,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= -github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spbsoluble/go-pkcs12 v0.3.3 h1:3nh7IKn16RDpmrSMtOu1JvbB0XHYq1j+IsICdU1c7J4= @@ -70,12 +70,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -83,10 +83,10 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 8dc5fd16f5559b083bcd5cf2b506b1c620fe03f3 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:24:50 -0700 Subject: [PATCH 07/29] fix(certs): For V2 PFX enrollments don't require `subject` and update validation logic to require a `subject` or at least 1 `SAN` --- v3/api/certificate.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/v3/api/certificate.go b/v3/api/certificate.go index 95a7af8..8775a50 100644 --- a/v3/api/certificate.go +++ b/v3/api/certificate.go @@ -151,7 +151,11 @@ func (c *Client) EnrollPFXV2(ea *EnrollPFXFctArgsV2) (*EnrollResponseV2, error) } ea.SubjectString = subject } else { - return nil, fmt.Errorf("subject is required to use enrollpfx(). Please configure either SubjectString or Subject") + log.Println("[DEBUG] EnrollPFXV2: Subject is nil checks if there are SANs") + if ea.SANs == nil || (len(ea.SANs.DNS) == 0 && len(ea.SANs.URI) == 0 && len(ea.SANs.IP4) == 0 && + len(ea.SANs.IP6) == 0) { + return nil, fmt.Errorf("subject or subject alternative names are required to use enrollpfx(). Please configure either SubjectString or Subject or SANs") + } } } From de70b30f1a3d46e47b721a7afa2b6352c1386c11 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:25:53 -0700 Subject: [PATCH 08/29] feat(certs): Add fields `AdditionalEnrollmentFields`, `AlternativeKeyType`, `AlternativeKeyLength` to `EnrollPFXFctArgsV2` --- v3/api/certificate_models.go | 38 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/v3/api/certificate_models.go b/v3/api/certificate_models.go index 98604f6..0f0e8e5 100644 --- a/v3/api/certificate_models.go +++ b/v3/api/certificate_models.go @@ -47,27 +47,35 @@ type EnrollPFXFctArgs struct { type EnrollPFXFctArgsV2 struct { Stores []CertificateStore `json:"Stores,omitempty"` CustomFriendlyName string `json:"CustomFriendlyName,omitempty"` - Password string `json:"Password"` + Password string `json:"Password,omitempty"` PopulateMissingValuesFromAD bool `json:"PopulateMissingValuesFromAD"` // Configure the SubjectString field as the full string subject for the certificate. For example, if you don't have // subject fields individually separated, and the subject is already in the format required by RFC5280, use the SubjectString field. - SubjectString string `json:"Subject"` // If the certificate subject is not already in the format required by RFC5280, configure the subject fields using a CertificateSubject // struct, and EnrollPFX will automatically compile this information into a proper subject. - Subject *CertificateSubject `json:"-"` - IncludeChain bool `json:"IncludeChain"` - RenewalCertificateId int `json:"RenewalCertificateId,omitempty"` - CertificateAuthority string `json:"CertificateAuthority"` - Timestamp string `json:"Timestamp"` - Template string `json:"Template"` - SANs *SANs `json:"SANs,omitempty"` - Metadata map[string]interface{} `json:"Metadata,omitempty"` - CertFormat string `json:"-"` - InstallIntoExistingCertificateStores bool `json:"InstallIntoExistingCertificateStores,omitempty"` - ChainOrder string `json:"ChainOrder,omitempty"` - KeyType string `json:"KeyType,omitempty"` - KeyLength int `json:"KeyLength,omitempty"` + Subject *CertificateSubject `json:"-"` + SubjectString string `json:"Subject,omitempty"` + IncludeChain bool `json:"IncludeChain"` + IncludeSubjectHeader bool `json:"IncludeSubjectHeader,omitempty"` + RenewalCertificateId int `json:"RenewalCertificateId,omitempty"` + CertificateAuthority string `json:"CertificateAuthority"` + Timestamp string `json:"Timestamp"` + Template string `json:"Template"` + SANs *SANs `json:"SANs,omitempty"` + Metadata map[string]interface{} `json:"Metadata,omitempty"` + AdditionalEnrollmentFields *map[string]interface{} `json:"AdditionalEnrollmentFields,omitempty"` + CertFormat string `json:"-"` // Needs to be passed as header X-Certificate-Format + InstallIntoExistingCertificateStores bool `json:"InstallIntoExistingCertificateStores,omitempty"` + ChainOrder string `json:"ChainOrder,omitempty"` + AlternativeKeyType string `json:"AlternativeKeyType,omitempty"` // Requires Command 25.0.0+ + KeyType string `json:"KeyType,omitempty"` + AlternativeKeyLength int `json:"AlternativeKeyLength,omitempty"` // Requires Command 25.0.0+ + KeyLength int `json:"KeyLength,omitempty"` + Curve string `json:"Curve,omitempty"` + EnrollmentPatternId int `json:"EnrollmentPatternId,omitempty"` // Requires Command 25.1.0+ + OwnerRoleId int `json:"OwnerRoleId,omitempty"` // Requires Command 12.3.0+ + OwnerRoleName string `json:"OwnerRoleName,omitempty"` // Requires Command 12.3.0+ } // EnrollCSRFctArgs holds the function arguments used for calling the EnrollCSR method. From 2612c4c37f3cf500d757a885a0b59192940a2886 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Tue, 23 Sep 2025 14:55:44 -0700 Subject: [PATCH 09/29] feat(enrollmentpatterns): Add `/EnrollmentPattern` models and endpoints --- v3/api/enrollment_patterns.go | 270 +++++++++++++++++++++++++++ v3/api/enrollment_patterns_models.go | 222 ++++++++++++++++++++++ 2 files changed, 492 insertions(+) create mode 100644 v3/api/enrollment_patterns.go create mode 100644 v3/api/enrollment_patterns_models.go diff --git a/v3/api/enrollment_patterns.go b/v3/api/enrollment_patterns.go new file mode 100644 index 0000000..574feb0 --- /dev/null +++ b/v3/api/enrollment_patterns.go @@ -0,0 +1,270 @@ +// Copyright 2024 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 api + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "strconv" + "strings" +) + +// CreateEnrollmentPattern creates a new enrollment pattern with the provided properties +func (c *Client) CreateEnrollmentPattern( + req *EnrollmentPatternCreateRequest, + forceTemplateDefault ...bool, +) (*EnrollmentPatternResponse, error) { + log.Println("[INFO] Creating enrollment pattern with Keyfactor") + + // Validate required fields + var missingFields []string + if req.Name == "" { + missingFields = append(missingFields, "Name") + } + if req.Template == 0 { + missingFields = append(missingFields, "Template") + } + + if len(missingFields) > 0 { + return nil, errors.New("Required field(s) missing: " + strings.Join(missingFields, ", ")) + } + + // Set Keyfactor-specific headers + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + // Build URL with query parameters + endpoint := "EnrollmentPatterns" + if len(forceTemplateDefault) > 0 && forceTemplateDefault[0] { + endpoint += "?forceTemplateDefault=true" + } + + keyfactorAPIStruct := &request{ + Method: "POST", + Endpoint: endpoint, + Headers: headers, + Payload: req, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + jsonResp := &EnrollmentPatternResponse{} + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + + return jsonResp, nil +} + +// GetEnrollmentPatterns returns all enrollment patterns according to the provided filter and output parameters +func (c *Client) GetEnrollmentPatterns(params ...*EnrollmentPatternsQueryParams) ([]EnrollmentPatternResponse, error) { + log.Println("[INFO] Fetching enrollment patterns from Keyfactor") + + // Set Keyfactor-specific headers + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + // Build URL with query parameters + endpoint := "EnrollmentPatterns" + var queryParams []string + + if len(params) > 0 && params[0] != nil { + param := params[0] + if param.QueryString != "" { + queryParams = append(queryParams, "QueryString="+param.QueryString) + } + if param.PageReturned > 0 { + queryParams = append(queryParams, "PageReturned="+strconv.Itoa(param.PageReturned)) + } + if param.ReturnLimit > 0 { + queryParams = append(queryParams, "ReturnLimit="+strconv.Itoa(param.ReturnLimit)) + } + if param.SortField != "" { + queryParams = append(queryParams, "SortField="+param.SortField) + } + if param.SortAscending != nil { + queryParams = append(queryParams, "SortAscending="+strconv.Itoa(*param.SortAscending)) + } + } + + if len(queryParams) > 0 { + endpoint += "?" + strings.Join(queryParams, "&") + } + + keyfactorAPIStruct := &request{ + Method: "GET", + Endpoint: endpoint, + Headers: headers, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp []EnrollmentPatternResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + + return jsonResp, nil +} + +// GetEnrollmentPattern returns the enrollment pattern associated with the provided ID +func (c *Client) GetEnrollmentPattern(id int) (*EnrollmentPatternResponse, error) { + log.Printf("[INFO] Fetching enrollment pattern with ID %d from Keyfactor", id) + + if id <= 0 { + return nil, errors.New("ID must be a positive integer") + } + + // Set Keyfactor-specific headers + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + keyfactorAPIStruct := &request{ + Method: "GET", + Endpoint: fmt.Sprintf("EnrollmentPatterns/%d", id), + Headers: headers, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp EnrollmentPatternResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + + return &jsonResp, nil +} + +// UpdateEnrollmentPattern updates an enrollment pattern according to the provided properties and Keyfactor identifier +func (c *Client) UpdateEnrollmentPattern( + id int, + req *EnrollmentPatternRequest, + forceTemplateDefault ...bool, +) (*EnrollmentPatternResponse, error) { + log.Printf("[INFO] Updating enrollment pattern with ID %d in Keyfactor", id) + + if id <= 0 { + return nil, errors.New("ID must be a positive integer") + } + + // Validate required fields + var missingFields []string + if req.Name == "" { + missingFields = append(missingFields, "Name") + } + + if len(missingFields) > 0 { + return nil, errors.New("Required field(s) missing: " + strings.Join(missingFields, ", ")) + } + + // Set Keyfactor-specific headers + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + // Build URL with query parameters + endpoint := fmt.Sprintf("EnrollmentPatterns/%d", id) + if len(forceTemplateDefault) > 0 && forceTemplateDefault[0] { + endpoint += "?forceTemplateDefault=true" + } + + keyfactorAPIStruct := &request{ + Method: "PUT", + Endpoint: endpoint, + Headers: headers, + Payload: req, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp EnrollmentPatternResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + + return &jsonResp, nil +} + +// DeleteEnrollmentPattern deletes an enrollment pattern by ID +// Note: This method assumes DELETE is supported based on REST conventions, +// though it may not be explicitly defined in the provided schema +func (c *Client) DeleteEnrollmentPattern(id int) error { + log.Printf("[INFO] Deleting enrollment pattern with ID %d from Keyfactor", id) + + if id <= 0 { + return errors.New("ID must be a positive integer") + } + + // Set Keyfactor-specific headers + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + keyfactorAPIStruct := &request{ + Method: "DELETE", + Endpoint: fmt.Sprintf("EnrollmentPatterns/%d", id), + Headers: headers, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return err + } + + // Check if the response indicates success (2xx status codes) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("failed to delete enrollment pattern: HTTP %d", resp.StatusCode) + } + + return nil +} diff --git a/v3/api/enrollment_patterns_models.go b/v3/api/enrollment_patterns_models.go new file mode 100644 index 0000000..c8a2392 --- /dev/null +++ b/v3/api/enrollment_patterns_models.go @@ -0,0 +1,222 @@ +// Copyright 2024 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 api + +// EnrollmentPatternCreateRequest represents the request structure for creating a new enrollment pattern +type EnrollmentPatternCreateRequest struct { + Template int `json:"Template"` + Name string `json:"Name"` + Description *string `json:"Description,omitempty"` + TemplateDefault bool `json:"TemplateDefault,omitempty"` + AssociatedRoles []string `json:"AssociatedRoles,omitempty"` + UseADPermissions bool `json:"UseADPermissions,omitempty"` + CertificateAuthorities []int `json:"CertificateAuthorities,omitempty"` + AllowedEnrollmentTypes int `json:"AllowedEnrollmentTypes,omitempty"` + Regexes []EnrollmentPatternRegexesRequest `json:"Regexes,omitempty"` + MetadataFields []EnrollmentPatternMetadataFieldRequest `json:"MetadataFields,omitempty"` + RestrictCAs bool `json:"RestrictCAs,omitempty"` + Policies EnrollmentPatternPolicyRequest `json:"Policies"` + Defaults []EnrollmentPatternDefaultRequest `json:"Defaults,omitempty"` + EnrollmentFields []EnrollmentPatternFieldRequest `json:"EnrollmentFields,omitempty"` +} + +// EnrollmentPatternRequest represents the request structure for updating an enrollment pattern +type EnrollmentPatternRequest struct { + Name string `json:"Name"` + Description *string `json:"Description,omitempty"` + TemplateDefault bool `json:"TemplateDefault,omitempty"` + AssociatedRoles []string `json:"AssociatedRoles,omitempty"` + UseADPermissions bool `json:"UseADPermissions,omitempty"` + CertificateAuthorities []int `json:"CertificateAuthorities,omitempty"` + AllowedEnrollmentTypes int `json:"AllowedEnrollmentTypes,omitempty"` + Regexes []EnrollmentPatternRegexesRequest `json:"Regexes,omitempty"` + MetadataFields []EnrollmentPatternMetadataFieldRequest `json:"MetadataFields,omitempty"` + RestrictCAs bool `json:"RestrictCAs,omitempty"` + Policies EnrollmentPatternPolicyRequest `json:"Policies"` + Defaults []EnrollmentPatternDefaultRequest `json:"Defaults,omitempty"` + EnrollmentFields []EnrollmentPatternFieldRequest `json:"EnrollmentFields,omitempty"` +} + +// EnrollmentPatternResponse represents the response structure for enrollment pattern operations +type EnrollmentPatternResponse struct { + ID int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + Description *string `json:"Description,omitempty"` + Template *EnrollmentPatternTemplateResponse `json:"Template,omitempty"` + TemplateDefault bool `json:"TemplateDefault,omitempty"` + UseADPermissions bool `json:"UseADPermissions,omitempty"` + AssociatedRoles []EnrollmentPatternAssociatedRoleResponse `json:"AssociatedRoles,omitempty"` + CertificateAuthorities []EnrollmentPatternCAResponse `json:"CertificateAuthorities,omitempty"` + AllowedEnrollmentTypes *int `json:"AllowedEnrollmentTypes,omitempty"` + Regexes []EnrollmentPatternRegexesResponse `json:"Regexes,omitempty"` + MetadataFields []EnrollmentPatternMetadataFieldResponse `json:"MetadataFields,omitempty"` + RestrictCAs bool `json:"RestrictCAs,omitempty"` + Policies *EnrollmentPatternPolicyResponse `json:"Policies,omitempty"` + Defaults []EnrollmentPatternDefaultResponse `json:"Defaults,omitempty"` + EnrollmentFields []EnrollmentPatternFieldResponse `json:"EnrollmentFields,omitempty"` +} + +// EnrollmentPatternRegexesRequest represents regex validation rules for enrollment patterns +type EnrollmentPatternRegexesRequest struct { + SubjectPart string `json:"SubjectPart"` + Regex string `json:"Regex,omitempty"` + Error string `json:"Error,omitempty"` + CaseSensitive bool `json:"CaseSensitive,omitempty"` +} + +// EnrollmentPatternRegexesResponse represents regex validation rules in responses +type EnrollmentPatternRegexesResponse struct { + SubjectPart *string `json:"SubjectPart,omitempty"` + Regex *string `json:"Regex,omitempty"` + Error *string `json:"Error,omitempty"` + CaseSensitive bool `json:"CaseSensitive,omitempty"` +} + +// EnrollmentPatternPolicyRequest represents policy settings for enrollment patterns +type EnrollmentPatternPolicyRequest struct { + AllowKeyReuse *bool `json:"AllowKeyReuse,omitempty"` + AllowWildcards *bool `json:"AllowWildcards,omitempty"` + RFCEnforcement *bool `json:"RFCEnforcement,omitempty"` + CertificateOwnerRole *int `json:"CertificateOwnerRole,omitempty"` + DefaultCertificateOwnerRoleId *int `json:"DefaultCertificateOwnerRoleId,omitempty"` + DefaultCertificateOwnerRoleName *string `json:"DefaultCertificateOwnerRoleName,omitempty"` + DefaultCertificateOwnerOverride bool `json:"DefaultCertificateOwnerOverride,omitempty"` + PrimaryKeyAlgorithms []AlgorithmDataRequestV2 `json:"PrimaryKeyAlgorithms,omitempty"` + AlternativeKeyAlgorithms []AlgorithmDataRequestV2 `json:"AlternativeKeyAlgorithms,omitempty"` +} + +// EnrollmentPatternPolicyResponse represents policy settings in responses +type EnrollmentPatternPolicyResponse struct { + AllowKeyReuse *bool `json:"AllowKeyReuse,omitempty"` + AllowWildcards *bool `json:"AllowWildcards,omitempty"` + RFCEnforcement *bool `json:"RFCEnforcement,omitempty"` + CertificateOwnerRole *int `json:"CertificateOwnerRole,omitempty"` + DefaultCertificateOwnerRoleId *int `json:"DefaultCertificateOwnerRoleId,omitempty"` + DefaultCertificateOwnerRoleName *string `json:"DefaultCertificateOwnerRoleName,omitempty"` + DefaultCertificateOwnerOverride bool `json:"DefaultCertificateOwnerOverride,omitempty"` + PrimaryKeyAlgorithms []AlgorithmDataResponse `json:"PrimaryKeyAlgorithms,omitempty"` + AlternativeKeyAlgorithms []AlgorithmDataResponse `json:"AlternativeKeyAlgorithms,omitempty"` +} + +// EnrollmentPatternMetadataFieldRequest represents metadata field configuration for requests +type EnrollmentPatternMetadataFieldRequest struct { + Id int `json:"Id,omitempty"` + DefaultValue string `json:"DefaultValue,omitempty"` + Validation string `json:"Validation,omitempty"` + Enrollment int `json:"Enrollment,omitempty"` + Message string `json:"Message,omitempty"` + Options string `json:"Options,omitempty"` + DependsOn string `json:"DependsOn,omitempty"` + DependsOnValue string `json:"DependsOnValue,omitempty"` +} + +// EnrollmentPatternMetadataFieldResponse represents metadata field configuration in responses +type EnrollmentPatternMetadataFieldResponse struct { + Id *int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + DefaultValue *string `json:"DefaultValue,omitempty"` + Validation *string `json:"Validation,omitempty"` + Enrollment *int `json:"Enrollment,omitempty"` + Message *string `json:"Message,omitempty"` + Options *string `json:"Options,omitempty"` + DependsOn *string `json:"DependsOn,omitempty"` + DependsOnValue *string `json:"DependsOnValue,omitempty"` + DataType *int `json:"DataType,omitempty"` + Hint *string `json:"Hint,omitempty"` +} + +// EnrollmentPatternDefaultRequest represents default value settings for requests +type EnrollmentPatternDefaultRequest struct { + SubjectPart string `json:"SubjectPart"` + DefaultValue string `json:"DefaultValue,omitempty"` +} + +// EnrollmentPatternDefaultResponse represents default value settings in responses +type EnrollmentPatternDefaultResponse struct { + SubjectPart *string `json:"SubjectPart,omitempty"` + DefaultValue *string `json:"DefaultValue,omitempty"` +} + +// EnrollmentPatternFieldRequest represents enrollment field configuration for requests +type EnrollmentPatternFieldRequest struct { + Id int `json:"Id,omitempty"` + DefaultValue string `json:"DefaultValue,omitempty"` + Validation string `json:"Validation,omitempty"` + Enrollment int `json:"Enrollment,omitempty"` + Message string `json:"Message,omitempty"` + Options string `json:"Options,omitempty"` + DependsOn string `json:"DependsOn,omitempty"` + DependsOnValue string `json:"DependsOnValue,omitempty"` +} + +// EnrollmentPatternFieldResponse represents enrollment field configuration in responses +type EnrollmentPatternFieldResponse struct { + Id *int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + DefaultValue *string `json:"DefaultValue,omitempty"` + Validation *string `json:"Validation,omitempty"` + Enrollment *int `json:"Enrollment,omitempty"` + Message *string `json:"Message,omitempty"` + Options *string `json:"Options,omitempty"` + DependsOn *string `json:"DependsOn,omitempty"` + DependsOnValue *string `json:"DependsOnValue,omitempty"` + DataType *int `json:"DataType,omitempty"` + Hint *string `json:"Hint,omitempty"` +} + +// EnrollmentPatternTemplateResponse represents template information in responses +type EnrollmentPatternTemplateResponse struct { + Id *int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + CommonName *string `json:"CommonName,omitempty"` + DisplayName *string `json:"DisplayName,omitempty"` +} + +// EnrollmentPatternAssociatedRoleResponse represents associated role information in responses +type EnrollmentPatternAssociatedRoleResponse struct { + Id *int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` +} + +// EnrollmentPatternCAResponse represents certificate authority information in responses +type EnrollmentPatternCAResponse struct { + Id *int `json:"Id,omitempty"` + LogicalName *string `json:"LogicalName,omitempty"` + HostName *string `json:"HostName,omitempty"` + CAName *string `json:"CAName,omitempty"` +} + +// AlgorithmDataRequestV2 represents algorithm configuration for requests +type AlgorithmDataRequestV2 struct { + KeyType *string `json:"KeyType,omitempty"` + KeySize *int `json:"KeySize,omitempty"` + CurveName *string `json:"CurveName,omitempty"` +} + +// AlgorithmDataResponse represents algorithm configuration in responses +type AlgorithmDataResponse struct { + KeyType *string `json:"KeyType,omitempty"` + KeySize *int `json:"KeySize,omitempty"` + CurveName *string `json:"CurveName,omitempty"` +} + +// EnrollmentPatternsQueryParams represents query parameters for listing enrollment patterns +type EnrollmentPatternsQueryParams struct { + QueryString string `json:"queryString,omitempty"` + PageReturned int `json:"pageReturned,omitempty"` + ReturnLimit int `json:"returnLimit,omitempty"` + SortField string `json:"sortField,omitempty"` + SortAscending *int `json:"sortAscending,omitempty"` // 0=ascending, 1=descending +} From b1091890eebe5d90de61f3ee51a4eb8f654e4a77 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 24 Sep 2025 09:47:57 -0700 Subject: [PATCH 10/29] feat(certficates): Add `OwnerRoleId,OwnerRoleName,AltKeyAlgorithm,AltKeySizeInBits,AltKeyType,IssuedEmail,AltSigningAlgorithm,AltKeyTypeString,HasAltPrivateKey,CARecordId,Curve,EnrollmentPatternId` to `GetCertificateResponse` model --- v3/api/certificate_models.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/v3/api/certificate_models.go b/v3/api/certificate_models.go index 0f0e8e5..0dd8021 100644 --- a/v3/api/certificate_models.go +++ b/v3/api/certificate_models.go @@ -221,16 +221,25 @@ type GetCertificateResponse struct { NotAfter string `json:"NotAfter"` IssuerDN string `json:"IssuerDN"` PrincipalId string `json:"PrincipalId"` + OwnerRoleId int `json:"OwnerRoleId;omitempty"` // Requires Command 12.3.0+ + OwnerRoleName string `json:"OwnerRoleName,omitempty"` // Requires Command 12.3.0+ TemplateId int `json:"TemplateId"` CertState int `json:"CertState"` KeySizeInBits int `json:"KeySizeInBits"` KeyType int `json:"KeyType"` + KeyAlgorithm string `json:"KeyAlgorithm"` + AltKeyAlgorithm string `json:"AltKeyAlgorithm,omitempty"` // Requires Command 25.0.0+ + AltKeySizeInBits int `json:"AltKeySizeInBits,omitempty"` // Requires Command 25.0.0+ + AltKeyType int `json:"AltKeyType,omitempty"` // Requires Command 25.0.0+ RequesterId int `json:"RequesterId"` IssuedOU string `json:"IssuedOU"` + IssuedEmail string `json:"IssuedEmail"` KeyUsage int `json:"KeyUsage"` SigningAlgorithm string `json:"SigningAlgorithm"` + AltSigningAlgorithm string `json:"AltSigningAlgorithm,omitempty"` // Requires Command 25.0.0+ CertStateString string `json:"CertStateString"` KeyTypeString string `json:"KeyTypeString"` + AltKeyTypeString string `json:"AltKeyTypeString,omitempty"` // Requires Command 25.0.0+ RevocationEffDate string `json:"RevocationEffDate"` RevocationReason int `json:"RevocationReason"` RevocationComment string `json:"RevocationComment"` @@ -239,6 +248,7 @@ type GetCertificateResponse struct { TemplateName string `json:"TemplateName"` ArchivedKey bool `json:"ArchivedKey"` HasPrivateKey bool `json:"HasPrivateKey"` + HasAltPrivateKey bool `json:"HasAltPrivateKey,omitempty"` // Requires Command 25.0.0+ PrincipalName string `json:"PrincipalName"` CertRequestId int `json:"CertRequestId"` RequesterName string `json:"RequesterName"` @@ -252,8 +262,11 @@ type GetCertificateResponse struct { Metadata interface{} `json:"Metadata"` CertificateKeyId int `json:"CertificateKeyId"` CARowIndex int `json:"CARowIndex"` + CARecordId string `json:"CARecordId"` DetailedKeyUsage []DetailedKeyUsage `json:"detailed_key_usage"` KeyRecoverable bool `json:"KeyRecoverable"` + Curve string `json:"Curve,omitempty"` + EnrollmentPatternId int `json:"EnrollmentPatternId,omitempty"` // Requires Command 25.1.0+ } type ListCertificateResponse struct { From 5175bb8d3dfe554c5db9f15960cd2a4a10b7321a Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 24 Sep 2025 09:48:57 -0700 Subject: [PATCH 11/29] fix(stores): Change `Password` type from `UpdateStorePasswordConfig` to `StorePasswordConfig` on `UpdateStoreFctArgs` --- v3/api/store_models.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/v3/api/store_models.go b/v3/api/store_models.go index bf4db2c..a210cad 100644 --- a/v3/api/store_models.go +++ b/v3/api/store_models.go @@ -51,14 +51,14 @@ type UpdateStoreFctArgs struct { // automatically populated by the CreateStore method. However, if configured, this field will be used. PropertiesString string `json:"Properties,omitempty"` // Mapped name-value pair field used to configure properties. - Properties map[string]interface{} `json:"-"` - AgentId string `json:"AgentId"` - AgentAssigned *bool `json:"AgentAssigned,omitempty"` - ContainerName *string `json:"ContainerName,omitempty"` - InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` - ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` - SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` - Password *UpdateStorePasswordConfig `json:"Password"` + Properties map[string]interface{} `json:"-"` + AgentId string `json:"AgentId"` + AgentAssigned *bool `json:"AgentAssigned,omitempty"` + ContainerName *string `json:"ContainerName,omitempty"` + InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` + ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` + SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` + Password *StorePasswordConfig `json:"Password"` } type UpdateStorePasswordConfig struct { From bb177bc5c58f3aade6c97b23babdb81b1ce6cf8c Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:04:49 -0700 Subject: [PATCH 12/29] fix(certs): Check that `Template` and `EnrollmentPattern` are not both empty for enrollments --- v3/api/certificate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v3/api/certificate.go b/v3/api/certificate.go index 8775a50..eccb9e6 100644 --- a/v3/api/certificate.go +++ b/v3/api/certificate.go @@ -113,8 +113,8 @@ func (c *Client) EnrollPFXV2(ea *EnrollPFXFctArgsV2) (*EnrollResponseV2, error) var missingFields []string // TODO: Probably a better way to express these if blocks - if ea.Template == "" { - missingFields = append(missingFields, "Template") + if ea.Template == "" && ea.EnrollmentPatternId == 0 { + missingFields = append(missingFields, "Template or EnrollmentPatternId") } if ea.CertificateAuthority == "" { missingFields = append(missingFields, "CertificateAuthority") From 02ac064269c83b5e1f0ac4c0decf85f1f552629f Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:24:09 -0700 Subject: [PATCH 13/29] fix(certs): Add `ChangeCertificateOwnerRole` --- v3/api/certificate.go | 62 ++++++++++++++++++++++++++++++++++++ v3/api/certificate_models.go | 12 +++++++ 2 files changed, 74 insertions(+) diff --git a/v3/api/certificate.go b/v3/api/certificate.go index eccb9e6..a73794b 100644 --- a/v3/api/certificate.go +++ b/v3/api/certificate.go @@ -768,6 +768,68 @@ func (c *Client) RecoverCertificate( return priv, leaf, chain, nil } +// ChangeCertificateOwnerRole changes the certificate's owner. Users must be in the current owner's role and the new owner's role. +// If removing the owner, leave both NewRoleId and NewRoleName empty in the request. +// Calls PUT /Certificates/{id}/Owner endpoint. +func (c *Client) ChangeCertificateOwnerRole( + certificateId int, + req *OwnerRequest, + params ...*CertificateOwnerChangeParams, +) error { + log.Printf("[INFO] Changing owner of certificate with ID %d in Keyfactor", certificateId) + + // Validate certificate ID + if certificateId <= 0 { + return errors.New("certificate ID must be a positive integer") + } + + // Set Keyfactor-specific headers + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + // Build URL with query parameters + endpoint := fmt.Sprintf("Certificates/%d/Owner", certificateId) + var queryParams []string + + if len(params) > 0 && params[0] != nil { + param := params[0] + if param.CollectionId != nil { + queryParams = append(queryParams, fmt.Sprintf("collectionId=%d", *param.CollectionId)) + } + if param.ContainerId != nil { + queryParams = append(queryParams, fmt.Sprintf("containerId=%d", *param.ContainerId)) + } + } + + if len(queryParams) > 0 { + endpoint += "?" + strings.Join(queryParams, "&") + } + + keyfactorAPIStruct := &request{ + Method: "PUT", + Endpoint: endpoint, + Headers: headers, + Payload: req, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return err + } + + // Check if the response indicates success (204 No Content expected) + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("failed to change certificate owner: HTTP %d", resp.StatusCode) + } + + return nil +} + // createSubject builds the certificate subject string from a passed CertificateSubject argument. func createSubject(cs CertificateSubject) (string, error) { var subject string diff --git a/v3/api/certificate_models.go b/v3/api/certificate_models.go index 0dd8021..e1c2b8b 100644 --- a/v3/api/certificate_models.go +++ b/v3/api/certificate_models.go @@ -340,3 +340,15 @@ type SubjectAltNameElements struct { type downloadCertificateResponse struct { Content string `json:"Content"` } + +// OwnerRequest represents the request structure for changing certificate ownership +type OwnerRequest struct { + NewRoleId *int `json:"NewRoleId,omitempty"` + NewRoleName *string `json:"NewRoleName,omitempty"` +} + +// CertificateOwnerChangeParams represents the parameters for changing certificate ownership +type CertificateOwnerChangeParams struct { + CollectionId *int `json:"collectionId,omitempty"` + ContainerId *int `json:"containerId,omitempty"` +} From c2ae7d3cca05b83cb15394e498ff602c4126e8b7 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:21:08 -0700 Subject: [PATCH 14/29] feat(certs): Add the following fields to `EnrollCSRFctArgs`: `PrivateKey,RenewalCertificateId,AdditionalEnrollmentFields,EnrollmentPatternId,OwnerRoleId,OwnerRoleName,IncludeSubjectHeader` --- v3/api/certificate_models.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/v3/api/certificate_models.go b/v3/api/certificate_models.go index e1c2b8b..945ab65 100644 --- a/v3/api/certificate_models.go +++ b/v3/api/certificate_models.go @@ -80,14 +80,21 @@ type EnrollPFXFctArgsV2 struct { // EnrollCSRFctArgs holds the function arguments used for calling the EnrollCSR method. type EnrollCSRFctArgs struct { - CSR string - Timestamp string `json:"Timestamp"` - Template string `json:"Template"` - CertFormat string `json:"-"` - CertificateAuthority string `json:"CertificateAuthority"` - IncludeChain bool `json:"IncludeChain"` - SANs *SANs `json:"SANs"` - Metadata map[string]interface{} `json:"Metadata"` + CSR string `json:"CSR"` //required + PrivateKey string `json:"PrivateKey,omitempty"` + RenewalCertificateId int `json:"RenewalCertificateId,omitempty"` + CertificateAuthority string `json:"CertificateAuthority,omitempty"` + IncludeChain bool `json:"IncludeChain"` + IncludeSubjectHeader bool `json:"IncludeSubjectHeader,omitempty"` + Timestamp string `json:"Timestamp"` + Template string `json:"Template,omitempty"` + EnrollmentPatternId int `json:"EnrollmentPatternId,omitempty"` // Requires Command 25.1.0+ + CertFormat string `json:"-"` + SANs *SANs `json:"SANs,omitempty"` + Metadata map[string]interface{} `json:"Metadata,omitempty"` + AdditionalEnrollmentFields map[string]interface{} `json:"AdditionalEnrollmentFields,omitempty"` + OwnerRoleId int `json:"OwnerRoleId,omitempty"` // Requires Command 12.3.0+ + OwnerRoleName string `json:"OwnerRoleName,omitempty"` // Requires Command 12.3.0+ } // RevokeCertArgs holds the function arguments used for calling the RevokeCert method. From f82208c941177c623d56a3edfdd2dc4c635d8608 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Sun, 28 Sep 2025 13:25:20 -0700 Subject: [PATCH 15/29] feat(certs): Add base64 response from `DownloadCertificate` --- v3/api/certificate.go | 127 ++++++++++++++++++++++++++++++++---------- 1 file changed, 99 insertions(+), 28 deletions(-) diff --git a/v3/api/certificate.go b/v3/api/certificate.go index a73794b..bd01c40 100644 --- a/v3/api/certificate.go +++ b/v3/api/certificate.go @@ -195,13 +195,16 @@ func (c *Client) EnrollPFXV2(ea *EnrollPFXFctArgsV2) (*EnrollResponseV2, error) // Returns: // - Leaf certificate // - Certificate chain +// - Raw certificate data (as base64 string, if applicable) +// - Error func (c *Client) DownloadCertificate( certId int, thumbprint string, serialNumber string, issuerDn string, collectionId int, -) (*x509.Certificate, []*x509.Certificate, error) { + certificateFormat string, +) (*x509.Certificate, []*x509.Certificate, *string, error) { log.Println("[INFO] Downloading certificate") /* The download certificate endpoint requires one of the following to retrieve a cert: @@ -221,7 +224,7 @@ func (c *Client) DownloadCertificate( } if !validInput { - return nil, nil, fmt.Errorf("certID, thumbprint, or serial number AND issuer DN required to dowload certificate") + return nil, nil, nil, fmt.Errorf("certID, thumbprint, or serial number AND issuer DN required to dowload certificate") } payload := &downloadCertificateBody{ @@ -247,11 +250,19 @@ func (c *Client) DownloadCertificate( } // Set Keyfactor-specific headers + switch certificateFormat { + case "CER", "CRT", "DER", "PEM": + // do nothing these are valid formats + break + default: + // if not specified or invalid format then default to P7B + certificateFormat = "P7B" + } headers := &apiHeaders{ Headers: []StringTuple{ {"x-keyfactor-api-version", "1"}, {"x-keyfactor-requested-with", "APIClient"}, - {"x-certificateformat", "P7B"}, + {"x-certificateformat", certificateFormat}, }, } @@ -265,13 +276,13 @@ func (c *Client) DownloadCertificate( resp, err := c.sendRequest(keyfactorAPIStruct) if err != nil { - return nil, nil, err + return nil, nil, nil, err } jsonResp := &downloadCertificateResponse{} err = json.NewDecoder(resp.Body).Decode(&jsonResp) if err != nil { - return nil, nil, err + return nil, nil, nil, err } //buf, err := base64.StdEncoding.DecodeString(jsonResp.Content) //if err != nil { @@ -285,17 +296,17 @@ func (c *Client) DownloadCertificate( certs, p7bErr := ConvertBase64P7BtoCertificates(jsonResp.Content) if p7bErr != nil { - return nil, nil, p7bErr + return nil, nil, &jsonResp.Content, p7bErr } var leaf *x509.Certificate if len(certs) > 1 { //leaf is last cert in chain leaf = certs[0] // First cert in chain is the leaf - return leaf, certs, nil + return leaf, certs, &jsonResp.Content, nil } - return certs[0], nil, nil + return certs[0], nil, &jsonResp.Content, nil } // EnrollCSR takes arguments for EnrollCSRFctArgs to enroll a passed Certificate Signing @@ -663,7 +674,8 @@ func (c *Client) RecoverCertificate( issuerDn string, password string, collectionId int, -) (interface{}, *x509.Certificate, []*x509.Certificate, error) { + certificateFormat string, +) (interface{}, *x509.Certificate, []*x509.Certificate, *string, error) { log.Println("[DEBUG] Enter RecoverCertificate") log.Println("[INFO] Recovering certificate ID:", certId) /* The download certificate endpoint requires one of the following to retrieve a cert: @@ -673,6 +685,9 @@ func (c *Client) RecoverCertificate( Check for this input */ + if certificateFormat == "" { + certificateFormat = "PFX" + } validInput := false if certId != 0 { validInput = true @@ -684,12 +699,12 @@ func (c *Client) RecoverCertificate( if !validInput { log.Println("[ERROR] RecoverCertificate: certID, thumbprint, or serial number AND issuer DN required to download certificate") - return nil, nil, nil, fmt.Errorf("certID, thumbprint, or serial number AND issuer DN required to download certificate") + return nil, nil, nil, nil, fmt.Errorf("certID, thumbprint, or serial number AND issuer DN required to download certificate") } log.Println("[DEBUG] RecoverCertificate: Valid input") if password == "" { - return nil, nil, nil, fmt.Errorf("password required to recover private key with certificate") + return nil, nil, nil, nil, fmt.Errorf("password required to recover private key with certificate") } rca := &recoverCertArgs{ @@ -707,7 +722,7 @@ func (c *Client) RecoverCertificate( Headers: []StringTuple{ {"x-keyfactor-api-version", "1"}, {"x-keyfactor-requested-with", "APIClient"}, - {"x-certificateformat", "PFX"}, + {"x-certificateformat", certificateFormat}, }, } @@ -738,7 +753,7 @@ func (c *Client) RecoverCertificate( resp, err := c.sendRequest(keyfactorAPIStruct) if err != nil { log.Println("[ERROR] RecoverCertificate: Error recovering certificate from Keyfactor Command", err.Error()) - return nil, nil, nil, err + return nil, nil, nil, nil, err } jsonResp := &recoverCertResponse{} @@ -746,26 +761,82 @@ func (c *Client) RecoverCertificate( err = json.NewDecoder(resp.Body).Decode(&jsonResp) if err != nil { log.Println("[ERROR] RecoverCertificate: Error decoding response from Keyfactor Command", err.Error()) - return nil, nil, nil, err + return nil, nil, nil, nil, err } - log.Println("[DEBUG] RecoverCertificate: Decoding PFX") - pfxDer, err := base64.StdEncoding.DecodeString(jsonResp.PFX) - if err != nil { - log.Println("[ERROR] RecoverCertificate: Error decoding PFX", err.Error()) - return nil, nil, nil, err - } + switch certificateFormat { + case "PFX", "pfx", "pkcs12", "p12", "jks", "JKS": + log.Println("[DEBUG] RecoverCertificate: decoding `PFX` response field") + pfxDer := jsonResp.PFX + if pfxDer == "" { + log.Println("[ERROR] RecoverCertificate: Error decoding PFX", err.Error()) + return nil, nil, nil, &pfxDer, fmt.Errorf("pfx field in response is empty") + } + log.Println("[INFO] Recovered certificate successfully") + log.Println("[DEBUG] RecoverCertificate returning in PFX format") + return nil, nil, nil, &pfxDer, nil + case "PEM", "pem": + log.Println("[DEBUG] RecoverCertificate: Decoding PFX") + pfxDer, dErr := base64.StdEncoding.DecodeString(jsonResp.PFX) + if dErr != nil { + log.Println("[ERROR] RecoverCertificate: Error decoding PFX", dErr.Error()) + return nil, nil, nil, &jsonResp.PFX, dErr + } - log.Println("[DEBUG] RecoverCertificate: Decoding PFX chain") - priv, leaf, chain, err := pkcs12.DecodeChain(pfxDer, rca.Password) - if err != nil { - log.Println("[ERROR] RecoverCertificate: Error decoding PFX chain", err.Error()) - return nil, nil, nil, err + log.Println("[DEBUG] RecoverCertificate: Decoding PFX chain") + priv, leaf, chain, pErr := pkcs12.DecodeChain(pfxDer, rca.Password) + if pErr != nil { + log.Println("[ERROR] RecoverCertificate: Error decoding PFX chain", pErr.Error()) + return nil, nil, nil, &jsonResp.PFX, pErr + } + + log.Println("[INFO] Recovered certificate successfully") + log.Println("[DEBUG] RecoverCertificate: ", leaf, chain) + return priv, leaf, chain, &jsonResp.PFX, nil + default: + log.Println("[DEBUG] RecoverCertificate: Decoding PFX") + pfxDer, dErr := base64.StdEncoding.DecodeString(jsonResp.PFX) + if dErr != nil { + log.Println("[ERROR] RecoverCertificate: Error decoding PFX", dErr.Error()) + return nil, nil, nil, &jsonResp.PFX, dErr + } + + log.Println("[DEBUG] RecoverCertificate: Decoding PFX chain") + priv, leaf, chain, pErr := pkcs12.DecodeChain(pfxDer, rca.Password) + if pErr != nil { + log.Println("[ERROR] RecoverCertificate: Error decoding PFX chain", pErr.Error()) + return nil, nil, nil, &jsonResp.PFX, pErr + } + + log.Println("[INFO] Recovered certificate successfully") + log.Println("[DEBUG] RecoverCertificate returning in PEM format") + + var pemCerts []string + + // Encode leaf certificate to PEM + pemLeaf := pem.EncodeToMemory( + &pem.Block{ + Type: "CERTIFICATE", + Bytes: leaf.Raw, + }, + ) + pemCerts = append(pemCerts, string(pemLeaf)) + + // Encode chain certificates to PEM + for _, cert := range chain { + pemCert := pem.EncodeToMemory( + &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }, + ) + pemCerts = append(pemCerts, string(pemCert)) + } + + pemData := strings.Join(pemCerts, "\n") + return priv, leaf, chain, &pemData, nil } - log.Println("[INFO] Recovered certificate successfully") - log.Println("[DEBUG] RecoverCertificate: ", leaf, chain) - return priv, leaf, chain, nil } // ChangeCertificateOwnerRole changes the certificate's owner. Users must be in the current owner's role and the new owner's role. From 6868250fcac81776bcfb6b495f11a22ef25d2686 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:00:48 -0700 Subject: [PATCH 16/29] fix(patterns): Fix enrollmentpattern models --- v3/api/enrollment_patterns_models.go | 97 ++++++++++++++-------------- 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/v3/api/enrollment_patterns_models.go b/v3/api/enrollment_patterns_models.go index c8a2392..0d2ac42 100644 --- a/v3/api/enrollment_patterns_models.go +++ b/v3/api/enrollment_patterns_models.go @@ -35,7 +35,7 @@ type EnrollmentPatternCreateRequest struct { // EnrollmentPatternRequest represents the request structure for updating an enrollment pattern type EnrollmentPatternRequest struct { Name string `json:"Name"` - Description *string `json:"Description,omitempty"` + Description string `json:"Description,omitempty"` TemplateDefault bool `json:"TemplateDefault,omitempty"` AssociatedRoles []string `json:"AssociatedRoles,omitempty"` UseADPermissions bool `json:"UseADPermissions,omitempty"` @@ -52,8 +52,8 @@ type EnrollmentPatternRequest struct { // EnrollmentPatternResponse represents the response structure for enrollment pattern operations type EnrollmentPatternResponse struct { ID int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` - Description *string `json:"Description,omitempty"` + Name string `json:"Name,omitempty"` + Description string `json:"Description,omitempty"` Template *EnrollmentPatternTemplateResponse `json:"Template,omitempty"` TemplateDefault bool `json:"TemplateDefault,omitempty"` UseADPermissions bool `json:"UseADPermissions,omitempty"` @@ -78,10 +78,10 @@ type EnrollmentPatternRegexesRequest struct { // EnrollmentPatternRegexesResponse represents regex validation rules in responses type EnrollmentPatternRegexesResponse struct { - SubjectPart *string `json:"SubjectPart,omitempty"` - Regex *string `json:"Regex,omitempty"` - Error *string `json:"Error,omitempty"` - CaseSensitive bool `json:"CaseSensitive,omitempty"` + SubjectPart string `json:"SubjectPart,omitempty"` + Regex string `json:"Regex,omitempty"` + Error string `json:"Error,omitempty"` + CaseSensitive bool `json:"CaseSensitive,omitempty"` } // EnrollmentPatternPolicyRequest represents policy settings for enrollment patterns @@ -99,12 +99,12 @@ type EnrollmentPatternPolicyRequest struct { // EnrollmentPatternPolicyResponse represents policy settings in responses type EnrollmentPatternPolicyResponse struct { - AllowKeyReuse *bool `json:"AllowKeyReuse,omitempty"` - AllowWildcards *bool `json:"AllowWildcards,omitempty"` - RFCEnforcement *bool `json:"RFCEnforcement,omitempty"` - CertificateOwnerRole *int `json:"CertificateOwnerRole,omitempty"` - DefaultCertificateOwnerRoleId *int `json:"DefaultCertificateOwnerRoleId,omitempty"` - DefaultCertificateOwnerRoleName *string `json:"DefaultCertificateOwnerRoleName,omitempty"` + AllowKeyReuse bool `json:"AllowKeyReuse,omitempty"` + AllowWildcards bool `json:"AllowWildcards,omitempty"` + RFCEnforcement bool `json:"RFCEnforcement,omitempty"` + CertificateOwnerRole int `json:"CertificateOwnerRole,omitempty"` + DefaultCertificateOwnerRoleId int `json:"DefaultCertificateOwnerRoleId,omitempty"` + DefaultCertificateOwnerRoleName string `json:"DefaultCertificateOwnerRoleName,omitempty"` DefaultCertificateOwnerOverride bool `json:"DefaultCertificateOwnerOverride,omitempty"` PrimaryKeyAlgorithms []AlgorithmDataResponse `json:"PrimaryKeyAlgorithms,omitempty"` AlternativeKeyAlgorithms []AlgorithmDataResponse `json:"AlternativeKeyAlgorithms,omitempty"` @@ -124,17 +124,12 @@ type EnrollmentPatternMetadataFieldRequest struct { // EnrollmentPatternMetadataFieldResponse represents metadata field configuration in responses type EnrollmentPatternMetadataFieldResponse struct { - Id *int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` - DefaultValue *string `json:"DefaultValue,omitempty"` - Validation *string `json:"Validation,omitempty"` - Enrollment *int `json:"Enrollment,omitempty"` - Message *string `json:"Message,omitempty"` - Options *string `json:"Options,omitempty"` - DependsOn *string `json:"DependsOn,omitempty"` - DependsOnValue *string `json:"DependsOnValue,omitempty"` - DataType *int `json:"DataType,omitempty"` - Hint *string `json:"Hint,omitempty"` + MetadataId int `json:"MetadataId,omitempty"` + DefaultValue string `json:"DefaultValue,omitempty"` + Validation string `json:"Validation,omitempty"` + Enrollment int `json:"Enrollment,omitempty"` + Message string `json:"Message,omitempty"` + CaseSensitive bool `json:"CaseSensitive,omitempty"` } // EnrollmentPatternDefaultRequest represents default value settings for requests @@ -145,8 +140,8 @@ type EnrollmentPatternDefaultRequest struct { // EnrollmentPatternDefaultResponse represents default value settings in responses type EnrollmentPatternDefaultResponse struct { - SubjectPart *string `json:"SubjectPart,omitempty"` - DefaultValue *string `json:"DefaultValue,omitempty"` + SubjectPart string `json:"SubjectPart,omitempty"` + Value string `json:"Value,omitempty"` } // EnrollmentPatternFieldRequest represents enrollment field configuration for requests @@ -163,39 +158,41 @@ type EnrollmentPatternFieldRequest struct { // EnrollmentPatternFieldResponse represents enrollment field configuration in responses type EnrollmentPatternFieldResponse struct { - Id *int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` - DefaultValue *string `json:"DefaultValue,omitempty"` - Validation *string `json:"Validation,omitempty"` - Enrollment *int `json:"Enrollment,omitempty"` - Message *string `json:"Message,omitempty"` - Options *string `json:"Options,omitempty"` - DependsOn *string `json:"DependsOn,omitempty"` - DependsOnValue *string `json:"DependsOnValue,omitempty"` - DataType *int `json:"DataType,omitempty"` - Hint *string `json:"Hint,omitempty"` + Id int `json:"Id,omitempty"` + Name string `json:"Name,omitempty"` + DefaultValue string `json:"DefaultValue,omitempty"` + Validation string `json:"Validation,omitempty"` + Enrollment int `json:"Enrollment,omitempty"` + Message string `json:"Message,omitempty"` + Options []string `json:"Options,omitempty"` + DependsOn string `json:"DependsOn,omitempty"` + DependsOnValue string `json:"DependsOnValue,omitempty"` + DataType int `json:"DataType,omitempty"` + Hint string `json:"Hint,omitempty"` } // EnrollmentPatternTemplateResponse represents template information in responses type EnrollmentPatternTemplateResponse struct { - Id *int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` - CommonName *string `json:"CommonName,omitempty"` - DisplayName *string `json:"DisplayName,omitempty"` + Id int `json:"Id,omitempty"` + TemplateName string `json:"TemplateName,omitempty"` + CommonName string `json:"CommonName,omitempty"` + ConfigurationTenant string `json:"ConfigurationTenant,omitempty"` + RequiresApproval bool `json:"RequiresApproval,omitempty"` + FriendlyName string `json:"FriendlyName,omitempty"` } // EnrollmentPatternAssociatedRoleResponse represents associated role information in responses type EnrollmentPatternAssociatedRoleResponse struct { - Id *int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` + Id int `json:"Id,omitempty"` + Name string `json:"Name,omitempty"` } // EnrollmentPatternCAResponse represents certificate authority information in responses type EnrollmentPatternCAResponse struct { - Id *int `json:"Id,omitempty"` - LogicalName *string `json:"LogicalName,omitempty"` - HostName *string `json:"HostName,omitempty"` - CAName *string `json:"CAName,omitempty"` + Id int `json:"Id,omitempty"` + LogicalName string `json:"LogicalName,omitempty"` + HostName string `json:"HostName,omitempty"` + ConfigurationTenant string `json:"ConfigurationTenant,omitempty"` } // AlgorithmDataRequestV2 represents algorithm configuration for requests @@ -207,9 +204,9 @@ type AlgorithmDataRequestV2 struct { // AlgorithmDataResponse represents algorithm configuration in responses type AlgorithmDataResponse struct { - KeyType *string `json:"KeyType,omitempty"` - KeySize *int `json:"KeySize,omitempty"` - CurveName *string `json:"CurveName,omitempty"` + Name string `json:"Name,omitempty"` + BitLengths []int `json:"bit_lengths,omitempty"` + Curves []string `json:"curves,omitempty"` } // EnrollmentPatternsQueryParams represents query parameters for listing enrollment patterns From 0a01a433b644e7c67cb673cb286a89a6d1ff9075 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 3 Dec 2025 08:59:22 -0800 Subject: [PATCH 17/29] chore(deps): Update all deps to latest releases. --- go.mod | 6 ++--- go.sum | 8 +++---- v2/go.mod | 42 +++++++++++++++++------------------ v2/go.sum | 50 +++++++++++++++++++++++++++++++++++++++++ v3/go.mod | 28 +++++++++++------------ v3/go.sum | 66 +++++++++++++++++++++++++------------------------------ 6 files changed, 121 insertions(+), 79 deletions(-) diff --git a/go.mod b/go.mod index 5f08992..e23dbd4 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/Keyfactor/keyfactor-go-client -go 1.20 +go 1.24.0 require ( github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 github.com/spbsoluble/go-pkcs12 v0.3.3 - go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 + go.mozilla.org/pkcs7 v0.9.0 ) -require golang.org/x/crypto v0.11.0 // indirect +require golang.org/x/crypto v0.45.0 // indirect diff --git a/go.sum b/go.sum index 08ced4d..5d16674 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,7 @@ github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 h1:caLlzFCz2L4Dth/9wh+VlypFA github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2/go.mod h1:Z5pSk8YFGXHbKeQ1wTzVN8A4P/fZmtAwqu3NgBHbDOs= github.com/spbsoluble/go-pkcs12 v0.3.3 h1:3nh7IKn16RDpmrSMtOu1JvbB0XHYq1j+IsICdU1c7J4= github.com/spbsoluble/go-pkcs12 v0.3.3/go.mod h1:MAxKIUEIl/QVcua/I1L4Otyxl9UvLCCIktce2Tjz6Nw= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= +go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= diff --git a/v2/go.mod b/v2/go.mod index 3593a14..89eaeea 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -14,38 +14,36 @@ module github.com/Keyfactor/keyfactor-go-client/v2 -go 1.22 - -toolchain go1.23.2 +go 1.24.0 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.1.1-rc.0 + github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 github.com/Keyfactor/keyfactor-go-client v1.4.3 - github.com/Keyfactor/keyfactor-go-client-sdk v1.0.1 - github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 + github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/spbsoluble/go-pkcs12 v0.3.3 go.mozilla.org/pkcs7 v0.9.0 ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect - github.com/fatih/color v1.13.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/v2/go.sum b/v2/go.sum index cd297b3..a1e7be6 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -1,25 +1,47 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0/go.mod h1:hd8hTTIY3VmUVPRHNH7GVCHO3SHgXkJKZHReby/bnUQ= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/Keyfactor/keyfactor-auth-client-go v1.1.1-rc.0 h1:/N/7pBj/oTUM1cYga2NvKyA4q6nfE0acciJHZqKC9Ug= github.com/Keyfactor/keyfactor-auth-client-go v1.1.1-rc.0/go.mod h1:yw92P9gSYVEyWkiUAJFsb7hjhXa8slN1+yTQgjSgovM= +github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 h1:otC213b6CYzqeN9b3CRlH1Qj1hTFIN5nqPA8gTlHdLg= +github.com/Keyfactor/keyfactor-auth-client-go v1.3.0/go.mod h1:97vCisBNkdCK0l2TuvOSdjlpvQa4+GHsMut1UTyv1jo= github.com/Keyfactor/keyfactor-go-client v1.4.3 h1:CmGvWcuIbDRFM0PfYOQH6UdtAgplvZBpU++KTU8iseg= github.com/Keyfactor/keyfactor-go-client v1.4.3/go.mod h1:3ZymLNCaSazglcuYeNfm9nrzn22wcwLjIWURrnUygBo= github.com/Keyfactor/keyfactor-go-client-sdk v1.0.1 h1:cs8hhvsY3MJ2o1K11HLTRCjRT8SbsKhhi73Y4By2CI0= github.com/Keyfactor/keyfactor-go-client-sdk v1.0.1/go.mod h1:Z5pSk8YFGXHbKeQ1wTzVN8A4P/fZmtAwqu3NgBHbDOs= +github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2 h1:caLlzFCz2L4Dth/9wh+VlypFATmOMmCSQkCPKOKMxw8= +github.com/Keyfactor/keyfactor-go-client-sdk v1.0.2/go.mod h1:Z5pSk8YFGXHbKeQ1wTzVN8A4P/fZmtAwqu3NgBHbDOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -29,16 +51,25 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= +github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -51,11 +82,15 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -76,10 +111,19 @@ go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -90,8 +134,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/v3/go.mod b/v3/go.mod index d69aa62..70dc405 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -14,36 +14,36 @@ module github.com/Keyfactor/keyfactor-go-client/v3 -go 1.24 +go 1.24.0 toolchain go1.24.5 require ( github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 - github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/spbsoluble/go-pkcs12 v0.3.3 go.mozilla.org/pkcs7 v0.9.0 ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/fatih/color v1.18.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/v3/go.sum b/v3/go.sum index 2008907..ede870e 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -1,41 +1,37 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 h1:mrkDCdkMsD4l9wjFGhofFHFrV43Y3c53RSLKOCJ5+Ow= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 h1:otC213b6CYzqeN9b3CRlH1Qj1hTFIN5nqPA8gTlHdLg= github.com/Keyfactor/keyfactor-auth-client-go v1.3.0/go.mod h1:97vCisBNkdCK0l2TuvOSdjlpvQa4+GHsMut1UTyv1jo= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= -github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= +github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -58,24 +54,22 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= -github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spbsoluble/go-pkcs12 v0.3.3 h1:3nh7IKn16RDpmrSMtOu1JvbB0XHYq1j+IsICdU1c7J4= github.com/spbsoluble/go-pkcs12 v0.3.3/go.mod h1:MAxKIUEIl/QVcua/I1L4Otyxl9UvLCCIktce2Tjz6Nw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -83,10 +77,10 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From e2548ef8da16176b554cd714a73d740d488dcf93 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:13:07 -0800 Subject: [PATCH 18/29] fix(models): UpdateStoreFctArgs to use UpdateStorePasswordConfig for Password --- v3/api/store_models.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/v3/api/store_models.go b/v3/api/store_models.go index a210cad..bf4db2c 100644 --- a/v3/api/store_models.go +++ b/v3/api/store_models.go @@ -51,14 +51,14 @@ type UpdateStoreFctArgs struct { // automatically populated by the CreateStore method. However, if configured, this field will be used. PropertiesString string `json:"Properties,omitempty"` // Mapped name-value pair field used to configure properties. - Properties map[string]interface{} `json:"-"` - AgentId string `json:"AgentId"` - AgentAssigned *bool `json:"AgentAssigned,omitempty"` - ContainerName *string `json:"ContainerName,omitempty"` - InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` - ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` - SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` - Password *StorePasswordConfig `json:"Password"` + Properties map[string]interface{} `json:"-"` + AgentId string `json:"AgentId"` + AgentAssigned *bool `json:"AgentAssigned,omitempty"` + ContainerName *string `json:"ContainerName,omitempty"` + InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` + ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` + SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` + Password *UpdateStorePasswordConfig `json:"Password"` } type UpdateStorePasswordConfig struct { From f6ac9087776f1a03d2db01f6de77fdac087a2a64 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:02:00 -0800 Subject: [PATCH 19/29] feat(models): Add models for Pam/Types API --- v3/api/pam_types_models.go | 103 +++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 v3/api/pam_types_models.go diff --git a/v3/api/pam_types_models.go b/v3/api/pam_types_models.go new file mode 100644 index 0000000..d165df0 --- /dev/null +++ b/v3/api/pam_types_models.go @@ -0,0 +1,103 @@ +// 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 api + +// PamParameterDataType represents the data type of a PAM parameter +// 1 = string, 2 = secret +type PamParameterDataType int + +const ( + PamParameterDataTypeString PamParameterDataType = 1 + PamParameterDataTypeSecret PamParameterDataType = 2 +) + +// SecretType represents the type of secret in the system +// 0-4 are valid values +type SecretType int + +// ProviderTypeParameterResponse represents a parameter for a PAM provider type +type ProviderTypeParameterResponse struct { + Id int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + DisplayName *string `json:"DisplayName,omitempty"` + DataType PamParameterDataType `json:"DataType,omitempty"` + InstanceLevel bool `json:"InstanceLevel,omitempty"` +} + +// ProviderTypeResponse represents a PAM provider type +type ProviderTypeResponse struct { + Id string `json:"Id,omitempty"` // UUID format + Name *string `json:"Name,omitempty"` + Parameters *[]ProviderTypeParameterResponse `json:"Parameters,omitempty"` +} + +// ProviderTypeParameterCreateRequest represents a request to create a PAM provider type parameter +type ProviderTypeParameterCreateRequest struct { + Name string `json:"Name"` + DisplayName *string `json:"DisplayName,omitempty"` + DataType PamParameterDataType `json:"DataType,omitempty"` + InstanceLevel bool `json:"InstanceLevel,omitempty"` +} + +// ProviderTypeCreateRequest represents a request to create a PAM provider type +type ProviderTypeCreateRequest struct { + Name string `json:"Name"` + Parameters *[]ProviderTypeParameterCreateRequest `json:"Parameters,omitempty"` +} + +// ProviderCreateRequestProviderTypeParam represents a provider type parameter in a provider creation request +type ProviderCreateRequestProviderTypeParam struct { + Id int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + DisplayName *string `json:"DisplayName,omitempty"` + InstanceLevel bool `json:"InstanceLevel,omitempty"` +} + +// PamProviderTypeParam represents a provider type parameter (full model) for PAM operations +type PamProviderTypeParam struct { + Id int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + DisplayName *string `json:"DisplayName,omitempty"` + DataType PamParameterDataType `json:"DataType,omitempty"` + InstanceLevel bool `json:"InstanceLevel,omitempty"` + ProviderType *ProviderType `json:"ProviderType,omitempty"` +} + +// ProviderType represents a PAM provider type (full model) +type ProviderType struct { + Id string `json:"Id,omitempty"` // UUID format + Name *string `json:"Name,omitempty"` + ProviderTypeParams *[]PamProviderTypeParam `json:"ProviderTypeParams,omitempty"` +} + +// PamProviderTypeParamValue represents a parameter value for a PAM provider type +type PamProviderTypeParamValue struct { + Id int `json:"Id,omitempty"` + Value *string `json:"Value,omitempty"` + ParameterId int `json:"ParameterId,omitempty"` + InstanceId *int `json:"InstanceId,omitempty"` + InstanceGuid *string `json:"InstanceGuid,omitempty"` // UUID format + Provider *Provider `json:"Provider,omitempty"` + ProviderTypeParam *PamProviderTypeParam `json:"ProviderTypeParam,omitempty"` +} + +// PamProviderTypeParamValueResponse represents a parameter value response for a PAM provider type +type PamProviderTypeParamValueResponse struct { + Id int `json:"Id,omitempty"` + Value *string `json:"Value,omitempty"` + InstanceId *int `json:"InstanceId,omitempty"` + InstanceGuid *string `json:"InstanceGuid,omitempty"` // UUID format + ProviderTypeParam *ProviderTypeParameterResponse `json:"ProviderTypeParam,omitempty"` +} From 362fe173d7dfddac6b6b3062566179e7ee582900 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:02:39 -0800 Subject: [PATCH 20/29] feat(api): Add Pam/Types CRUD API functions. --- v3/api/pam_types.go | 159 ++++++++++++++ v3/api/pam_types_test.go | 432 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 591 insertions(+) create mode 100644 v3/api/pam_types.go create mode 100644 v3/api/pam_types_test.go diff --git a/v3/api/pam_types.go b/v3/api/pam_types.go new file mode 100644 index 0000000..3aa78f7 --- /dev/null +++ b/v3/api/pam_types.go @@ -0,0 +1,159 @@ +// 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 api + +import ( + "encoding/json" + "fmt" + "log" +) + +// ListPAMProviderTypes returns all PAM provider types in the Keyfactor instance +func (c *Client) ListPAMProviderTypes() (*[]ProviderTypeResponse, error) { + log.Println("[INFO] Listing all PAM provider types") + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + keyfactorAPIStruct := &request{ + Method: "GET", + Endpoint: "PamProviders/Types", + Headers: headers, + Payload: nil, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp []ProviderTypeResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +func (c *Client) GetPAMProviderTypeByName(name string) (*ProviderTypeResponse, error) { + // list all provider types + types, err := c.ListPAMProviderTypes() + if err != nil { + return nil, err + } + + // find the provider type with the matching name + for _, t := range *types { + if t.Name != nil && *t.Name == name { + return &t, nil + } + } + return nil, fmt.Errorf("PAM provider type with name '%s' not found", name) +} + +// GetPAMProviderType returns a specific PAM provider type by ID +func (c *Client) GetPAMProviderType(id string) (*ProviderTypeResponse, error) { + log.Printf("[INFO] Getting PAM provider type with ID: %s", id) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/Types/%s", id) + keyfactorAPIStruct := &request{ + Method: "GET", + Endpoint: endpoint, + Headers: headers, + Payload: nil, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp ProviderTypeResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// CreatePAMProviderType creates a new PAM provider type with the associated properties +func (c *Client) CreatePAMProviderType(providerType *ProviderTypeCreateRequest) (*ProviderTypeResponse, error) { + log.Printf("[INFO] Creating new PAM provider type: %s", providerType.Name) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + keyfactorAPIStruct := &request{ + Method: "POST", + Endpoint: "PamProviders/Types", + Headers: headers, + Payload: providerType, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp ProviderTypeResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// DeletePAMProviderType deletes a PAM provider type by ID, as long as it's not currently in use +func (c *Client) DeletePAMProviderType(id string) error { + log.Printf("[INFO] Deleting PAM provider type with ID: %s", id) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/Types/%s", id) + keyfactorAPIStruct := &request{ + Method: "DELETE", + Endpoint: endpoint, + Headers: headers, + Payload: nil, + } + + _, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return err + } + + return nil +} diff --git a/v3/api/pam_types_test.go b/v3/api/pam_types_test.go new file mode 100644 index 0000000..eac814a --- /dev/null +++ b/v3/api/pam_types_test.go @@ -0,0 +1,432 @@ +// 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 api + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" +) + +// mockAuthConfig implements AuthConfig interface for testing +type mockAuthConfig struct { + serverConfig *auth_providers.Server + httpClient *http.Client +} + +func (m *mockAuthConfig) GetServerConfig() *auth_providers.Server { + return m.serverConfig +} + +func (m *mockAuthConfig) GetHttpClient() (*http.Client, error) { + return m.httpClient, nil +} + +func (m *mockAuthConfig) Authenticate() error { + return nil +} + +// newTestClient creates a test client with mock server +func newTestClient(server *httptest.Server) *Client { + return &Client{ + AuthClient: &mockAuthConfig{ + serverConfig: &auth_providers.Server{ + Host: server.URL, + APIPath: "/KeyfactorAPI", + SkipTLSVerify: true, + }, + httpClient: server.Client(), + }, + } +} + +// Mock response data +var ( + mockProviderTypeId = "550e8400-e29b-41d4-a716-446655440000" + mockProviderTypeName = "CyberArk" + + mockProviderTypeResponse = ProviderTypeResponse{ + Id: mockProviderTypeId, + Name: &mockProviderTypeName, + Parameters: &[]ProviderTypeParameterResponse{ + { + Id: 1, + Name: stringPtr("Username"), + DisplayName: stringPtr("User Name"), + DataType: PamParameterDataTypeString, + InstanceLevel: false, + }, + { + Id: 2, + Name: stringPtr("Password"), + DisplayName: stringPtr("Password"), + DataType: PamParameterDataTypeSecret, + InstanceLevel: true, + }, + }, + } + + mockProviderResponseLegacy = ProviderResponseLegacy{ + Id: 1, + Name: stringPtr("Test Provider"), + Area: 1, + ProviderType: &ProviderType{ + Id: mockProviderTypeId, + Name: &mockProviderTypeName, + }, + SecuredAreaId: intPtr(1), + Remote: false, + } + + mockLocalPAMEntry = LocalPAMEntryResponse{ + ProviderId: 1, + SecretName: stringPtr("test-secret"), + Description: stringPtr("Test secret description"), + } +) + +// Helper functions +func stringPtr(s string) *string { + return &s +} + +func intPtr(i int) *int { + return &i +} + +func TestListPAMProviderTypes(t *testing.T) { + tests := []struct { + name string + mockResponse interface{} + mockStatusCode int + wantErr bool + wantCount int + }{ + { + name: "successful list", + mockResponse: []ProviderTypeResponse{ + mockProviderTypeResponse, + }, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 1, + }, + { + name: "empty list", + mockResponse: []ProviderTypeResponse{}, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 0, + }, + { + name: "server error", + mockResponse: map[string]string{"error": "internal server error"}, + mockStatusCode: http.StatusInternalServerError, + wantErr: true, + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // Verify request + if r.URL.Path != "/KeyfactorAPI/PamProviders/Types" { + t.Errorf("Expected path /KeyfactorAPI/PamProviders/Types, got %s", r.URL.Path) + } + if r.Method != "GET" { + t.Errorf("Expected GET method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.ListPAMProviderTypes() + if (err != nil) != tt.wantErr { + t.Errorf("ListPAMProviderTypes() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if len(*got) != tt.wantCount { + t.Errorf("ListPAMProviderTypes() got %d items, want %d", len(*got), tt.wantCount) + } + } + }, + ) + } +} + +func TestGetPAMProviderType(t *testing.T) { + tests := []struct { + name string + providerId string + mockResponse interface{} + mockStatusCode int + wantErr bool + }{ + { + name: "successful get", + providerId: mockProviderTypeId, + mockResponse: mockProviderTypeResponse, + mockStatusCode: http.StatusOK, + wantErr: false, + }, + { + name: "not found", + providerId: "nonexistent-id", + mockResponse: map[string]string{"error": "not found"}, + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/Types/" + tt.providerId + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "GET" { + t.Errorf("Expected GET method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.GetPAMProviderType(tt.providerId) + if (err != nil) != tt.wantErr { + t.Errorf("GetPAMProviderType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if got.Id != mockProviderTypeId { + t.Errorf("GetPAMProviderType() Id = %v, want %v", got.Id, mockProviderTypeId) + } + } + }, + ) + } +} + +func TestCreatePAMProviderType(t *testing.T) { + createRequest := &ProviderTypeCreateRequest{ + Name: "New Provider Type", + Parameters: &[]ProviderTypeParameterCreateRequest{ + { + Name: "ApiKey", + DisplayName: stringPtr("API Key"), + DataType: PamParameterDataTypeSecret, + InstanceLevel: true, + }, + }, + } + + tests := []struct { + name string + request *ProviderTypeCreateRequest + mockResponse interface{} + mockStatusCode int + wantErr bool + }{ + { + name: "successful create", + request: createRequest, + mockResponse: mockProviderTypeResponse, + mockStatusCode: http.StatusOK, + wantErr: false, + }, + { + name: "bad request", + request: createRequest, + mockResponse: map[string]string{"error": "invalid request"}, + mockStatusCode: http.StatusBadRequest, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/KeyfactorAPI/PamProviders/Types" { + t.Errorf("Expected path /KeyfactorAPI/PamProviders/Types, got %s", r.URL.Path) + } + if r.Method != "POST" { + t.Errorf("Expected POST method, got %s", r.Method) + } + + // Verify request body + var receivedRequest ProviderTypeCreateRequest + if err := json.NewDecoder(r.Body).Decode(&receivedRequest); err != nil { + t.Errorf("Failed to decode request body: %v", err) + } + if receivedRequest.Name != tt.request.Name { + t.Errorf("Expected name %s, got %s", tt.request.Name, receivedRequest.Name) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.CreatePAMProviderType(tt.request) + if (err != nil) != tt.wantErr { + t.Errorf("CreatePAMProviderType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if got.Id != mockProviderTypeId { + t.Errorf("CreatePAMProviderType() Id = %v, want %v", got.Id, mockProviderTypeId) + } + } + }, + ) + } +} + +func TestDeletePAMProviderType(t *testing.T) { + tests := []struct { + name string + providerId string + mockStatusCode int + wantErr bool + }{ + { + name: "successful delete", + providerId: mockProviderTypeId, + mockStatusCode: http.StatusNoContent, + wantErr: false, + }, + { + name: "not found", + providerId: "nonexistent-id", + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "in use", + providerId: mockProviderTypeId, + mockStatusCode: http.StatusConflict, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/Types/" + tt.providerId + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "DELETE" { + t.Errorf("Expected DELETE method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + err := client.DeletePAMProviderType(tt.providerId) + if (err != nil) != tt.wantErr { + t.Errorf("DeletePAMProviderType() error = %v, wantErr %v", err, tt.wantErr) + } + }, + ) + } +} + +func TestGetPAMProviderQuery_toQueryString(t *testing.T) { + tests := []struct { + name string + query *GetPAMProviderQuery + want string + }{ + { + name: "nil query", + query: nil, + want: "", + }, + { + name: "empty query", + query: &GetPAMProviderQuery{}, + want: "", + }, + { + name: "full query", + query: &GetPAMProviderQuery{ + QueryString: "Name -eq 'Test'", + PageReturned: 1, + ReturnLimit: 10, + SortField: "Name", + SortAscending: 0, + }, + want: "QueryString=Name -eq 'Test'&PageReturned=1&ReturnLimit=10&SortField=Name&SortAscending=0", + }, + { + name: "partial query", + query: &GetPAMProviderQuery{ + QueryString: "Name -eq 'Test'", + ReturnLimit: 10, + }, + want: "QueryString=Name -eq 'Test'&ReturnLimit=10", + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + got := tt.query.toQueryString() + if got != tt.want { + t.Errorf("GetPAMProviderQuery.toQueryString() = %v, want %v", got, tt.want) + } + }, + ) + } +} From f8bf9630c8892c880bed9f78590c0bc96aef2be6 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:03:02 -0800 Subject: [PATCH 21/29] feat(models): Add Pam provider API models --- v3/api/pam_models.go | 102 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 v3/api/pam_models.go diff --git a/v3/api/pam_models.go b/v3/api/pam_models.go new file mode 100644 index 0000000..7c675e4 --- /dev/null +++ b/v3/api/pam_models.go @@ -0,0 +1,102 @@ +package api + +// Provider represents a PAM provider (full model) +type Provider struct { + Id int `json:"Id,omitempty"` + Name string `json:"Name"` + Area int `json:"Area,omitempty"` + ProviderType ProviderType `json:"ProviderType"` + ProviderTypeParamValues *[]PamProviderTypeParamValue `json:"ProviderTypeParamValues,omitempty"` + SecuredAreaId *int `json:"SecuredAreaId,omitempty"` + Remote bool `json:"Remote,omitempty"` + IsInUse bool `json:"IsInUse,omitempty"` + IsLocalDB bool `json:"IsLocalDB,omitempty"` +} + +// ProviderCreateRequestTypeParamValue represents a parameter value in a provider creation request +type ProviderCreateRequestTypeParamValue struct { + Id int `json:"Id,omitempty"` + Value *string `json:"Value,omitempty"` + InstanceId *int `json:"InstanceId,omitempty"` + InstanceGuid *string `json:"InstanceGuid,omitempty"` // UUID format + ProviderTypeParam *ProviderCreateRequestProviderTypeParam `json:"ProviderTypeParam,omitempty"` +} + +// ProviderCreateRequestProviderType represents a provider type reference in a provider creation request +type ProviderCreateRequestProviderType struct { + Id string `json:"Id,omitempty"` // UUID format +} + +// ProviderCreateRequest represents a request to create a PAM provider +type ProviderCreateRequest struct { + Name string `json:"Name"` + Remote bool `json:"Remote,omitempty"` + Area int `json:"Area,omitempty"` + ProviderType ProviderType `json:"ProviderType"` + ProviderTypeParamValues *[]PamProviderTypeParamValue `json:"ProviderTypeParamValues,omitempty"` + SecuredAreaId *int `json:"SecuredAreaId,omitempty"` +} + +// ProviderUpdateRequestLegacy represents a request to update a PAM provider (legacy format) +type ProviderUpdateRequestLegacy struct { + Id int `json:"Id"` + Name string `json:"Name"` + Remote bool `json:"Remote,omitempty"` + Area int `json:"Area,omitempty"` + ProviderType ProviderType `json:"ProviderType"` + ProviderTypeParamValues *[]PamProviderTypeParamValue `json:"ProviderTypeParamValues,omitempty"` + SecuredAreaId *int `json:"SecuredAreaId,omitempty"` +} + +// ProviderResponseLegacy represents a PAM provider response (legacy format) +type ProviderResponseLegacy struct { + Id int `json:"Id,omitempty"` + Name *string `json:"Name,omitempty"` + Area int `json:"Area,omitempty"` + ProviderType *ProviderType `json:"ProviderType,omitempty"` + ProviderTypeParamValues *[]PamProviderTypeParamValueResponse `json:"ProviderTypeParamValues,omitempty"` + SecuredAreaId *int `json:"SecuredAreaId,omitempty"` + Remote bool `json:"Remote,omitempty"` +} + +// LocalPAMEntryCreateRequest represents a request to create a local PAM entry +type LocalPAMEntryCreateRequest struct { + SecretName string `json:"SecretName"` + Description *string `json:"Description,omitempty"` + SecretValue string `json:"SecretValue"` +} + +// LocalPAMEntryUpdateRequest represents a request to update a local PAM entry +type LocalPAMEntryUpdateRequest struct { + SecretName string `json:"SecretName"` + Description *string `json:"Description,omitempty"` + SecretValue *string `json:"SecretValue,omitempty"` +} + +// LocalPAMEntryResponse represents a local PAM entry response +type LocalPAMEntryResponse struct { + ProviderId int `json:"ProviderId,omitempty"` + SecretName *string `json:"SecretName,omitempty"` + Description *string `json:"Description,omitempty"` +} + +// KeyfactorSecret represents a Keyfactor secret +type KeyfactorSecret struct { + Value interface{} `json:"Value,omitempty"` + SecretTypeGuid string `json:"SecretTypeGuid,omitempty"` // UUID format + InstanceId *int `json:"InstanceId,omitempty"` + InstanceGuid *string `json:"InstanceGuid,omitempty"` // UUID format + ProviderTypeParameterValues *[]PamProviderTypeParamValue `json:"ProviderTypeParameterValues,omitempty"` + ProviderId *int `json:"ProviderId,omitempty"` + IsManaged bool `json:"IsManaged,omitempty"` + SecretType SecretType `json:"SecretType,omitempty"` + RemoteProviderName *string `json:"RemoteProviderName,omitempty"` + HasValue bool `json:"HasValue,omitempty"` +} + +// KeyfactorAPISecret represents a Keyfactor API secret +type KeyfactorAPISecret struct { + SecretValue *string `json:"SecretValue,omitempty"` + Parameters map[string]*string `json:"Parameters,omitempty"` + Provider *int `json:"Provider,omitempty"` +} From 3580c64bef6bc18772f5a08e97401efaf4da05a8 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:03:21 -0800 Subject: [PATCH 22/29] feat(api): Add Pam provider CRUD API functions --- v3/api/pam.go | 347 +++++++++++++++++++ v3/api/pam_test.go | 831 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1178 insertions(+) create mode 100644 v3/api/pam.go create mode 100644 v3/api/pam_test.go diff --git a/v3/api/pam.go b/v3/api/pam.go new file mode 100644 index 0000000..bb6fb49 --- /dev/null +++ b/v3/api/pam.go @@ -0,0 +1,347 @@ +package api + +import ( + "encoding/json" + "fmt" + "log" +) + +// ListPAMProviders returns all PAM providers according to the provided filter and output parameters +func (c *Client) ListPAMProviders(query *GetPAMProviderQuery) (*[]ProviderResponseLegacy, error) { + log.Println("[INFO] Listing all PAM providers") + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + endpoint := "PamProviders" + if query != nil { + queryParams := query.toQueryString() + if queryParams != "" { + endpoint = fmt.Sprintf("%s?%s", endpoint, queryParams) + } + } + + keyfactorAPIStruct := &request{ + Method: "GET", + Endpoint: endpoint, + Headers: headers, + Payload: nil, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp []ProviderResponseLegacy + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// GetPAMProvider returns a specific PAM provider by ID +func (c *Client) GetPAMProvider(id int) (*ProviderResponseLegacy, error) { + log.Printf("[INFO] Getting PAM provider with ID: %d", id) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/%d", id) + keyfactorAPIStruct := &request{ + Method: "GET", + Endpoint: endpoint, + Headers: headers, + Payload: nil, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp ProviderResponseLegacy + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// CreatePAMProvider creates a new PAM provider with the associated properties +func (c *Client) CreatePAMProvider(provider *ProviderCreateRequest) (*ProviderResponseLegacy, error) { + log.Printf("[INFO] Creating new PAM provider: %s", provider.Name) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + keyfactorAPIStruct := &request{ + Method: "POST", + Endpoint: "PamProviders", + Headers: headers, + Payload: provider, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp ProviderResponseLegacy + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// UpdatePAMProvider updates an existing PAM provider +func (c *Client) UpdatePAMProvider(provider *ProviderUpdateRequestLegacy) (*ProviderResponseLegacy, error) { + log.Printf("[INFO] Updating PAM provider with ID: %d", provider.Id) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + keyfactorAPIStruct := &request{ + Method: "PUT", + Endpoint: "PamProviders", + Headers: headers, + Payload: provider, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp ProviderResponseLegacy + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// DeletePAMProvider deletes a PAM provider by ID +func (c *Client) DeletePAMProvider(id int) error { + log.Printf("[INFO] Deleting PAM provider with ID: %d", id) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/%d", id) + keyfactorAPIStruct := &request{ + Method: "DELETE", + Endpoint: endpoint, + Headers: headers, + Payload: nil, + } + + _, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return err + } + + return nil +} + +// ListLocalPAMEntries returns local PAM entries for the given PAM provider according to the provided filter +func (c *Client) ListLocalPAMEntries(providerId int, query *GetPAMProviderQuery) (*[]LocalPAMEntryResponse, error) { + log.Printf("[INFO] Listing local PAM entries for provider ID: %d", providerId) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/Local/%d/Entries", providerId) + if query != nil { + queryParams := query.toQueryString() + if queryParams != "" { + endpoint = fmt.Sprintf("%s?%s", endpoint, queryParams) + } + } + + keyfactorAPIStruct := &request{ + Method: "GET", + Endpoint: endpoint, + Headers: headers, + Payload: nil, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp []LocalPAMEntryResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// CreateLocalPAMEntry creates a new local PAM entry for the given PAM provider +func (c *Client) CreateLocalPAMEntry(providerId int, entry *LocalPAMEntryCreateRequest) ( + *LocalPAMEntryResponse, + error, +) { + log.Printf("[INFO] Creating local PAM entry for provider ID: %d", providerId) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/Local/%d/Entries", providerId) + keyfactorAPIStruct := &request{ + Method: "POST", + Endpoint: endpoint, + Headers: headers, + Payload: entry, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp LocalPAMEntryResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// UpdateLocalPAMEntry updates an existing local PAM entry for the given PAM provider +func (c *Client) UpdateLocalPAMEntry( + providerId int, + secretName string, + entry *LocalPAMEntryUpdateRequest, +) (*LocalPAMEntryResponse, error) { + log.Printf("[INFO] Updating local PAM entry '%s' for provider ID: %d", secretName, providerId) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + {"Content-Type", "application/json"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/Local/%d/Entries/%s", providerId, secretName) + keyfactorAPIStruct := &request{ + Method: "PUT", + Endpoint: endpoint, + Headers: headers, + Payload: entry, + } + + resp, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return nil, err + } + + var jsonResp LocalPAMEntryResponse + err = json.NewDecoder(resp.Body).Decode(&jsonResp) + if err != nil { + return nil, err + } + return &jsonResp, nil +} + +// DeleteLocalPAMEntry deletes a local PAM entry for the given PAM provider +func (c *Client) DeleteLocalPAMEntry(providerId int, secretName string) error { + log.Printf("[INFO] Deleting local PAM entry '%s' for provider ID: %d", secretName, providerId) + + headers := &apiHeaders{ + Headers: []StringTuple{ + {"x-keyfactor-api-version", "1"}, + {"x-keyfactor-requested-with", "APIClient"}, + }, + } + + endpoint := fmt.Sprintf("PamProviders/Local/%d/Entries/%s", providerId, secretName) + keyfactorAPIStruct := &request{ + Method: "DELETE", + Endpoint: endpoint, + Headers: headers, + Payload: nil, + } + + _, err := c.sendRequest(keyfactorAPIStruct) + if err != nil { + return err + } + + return nil +} + +// GetPAMProviderQuery represents query parameters for PAM provider listing +type GetPAMProviderQuery struct { + QueryString string + PageReturned int + ReturnLimit int + SortField string + SortAscending int +} + +// toQueryString converts query parameters to URL query string +func (q *GetPAMProviderQuery) toQueryString() string { + if q == nil { + return "" + } + + params := "" + if q.QueryString != "" { + params += fmt.Sprintf("QueryString=%s&", q.QueryString) + } + if q.PageReturned > 0 { + params += fmt.Sprintf("PageReturned=%d&", q.PageReturned) + } + if q.ReturnLimit > 0 { + params += fmt.Sprintf("ReturnLimit=%d&", q.ReturnLimit) + } + if q.SortField != "" { + params += fmt.Sprintf("SortField=%s&", q.SortField) + // Only add SortAscending if SortField is provided + params += fmt.Sprintf("SortAscending=%d&", q.SortAscending) + } + + // Remove trailing '&' + if len(params) > 0 && params[len(params)-1] == '&' { + params = params[:len(params)-1] + } + + return params +} diff --git a/v3/api/pam_test.go b/v3/api/pam_test.go new file mode 100644 index 0000000..a199714 --- /dev/null +++ b/v3/api/pam_test.go @@ -0,0 +1,831 @@ +// 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 api + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestListPAMProviders(t *testing.T) { + tests := []struct { + name string + query *GetPAMProviderQuery + mockResponse interface{} + mockStatusCode int + wantErr bool + wantCount int + }{ + { + name: "successful list without query", + query: nil, + mockResponse: []ProviderResponseLegacy{ + mockProviderResponseLegacy, + }, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 1, + }, + { + name: "successful list with query", + query: &GetPAMProviderQuery{ + QueryString: "Name -eq 'Test'", + ReturnLimit: 10, + PageReturned: 1, + }, + mockResponse: []ProviderResponseLegacy{ + mockProviderResponseLegacy, + }, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 1, + }, + { + name: "empty list", + query: nil, + mockResponse: []ProviderResponseLegacy{}, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 0, + }, + { + name: "server error", + query: nil, + mockResponse: map[string]string{"error": "internal server error"}, + mockStatusCode: http.StatusInternalServerError, + wantErr: true, + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // Verify request path starts with expected endpoint + if !strings.HasPrefix(r.URL.Path, "/KeyfactorAPI/PamProviders") { + t.Errorf("Expected path to start with /KeyfactorAPI/PamProviders, got %s", r.URL.Path) + } + if r.Method != "GET" { + t.Errorf("Expected GET method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.ListPAMProviders(tt.query) + if (err != nil) != tt.wantErr { + t.Errorf("ListPAMProviders() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if len(*got) != tt.wantCount { + t.Errorf("ListPAMProviders() got %d items, want %d", len(*got), tt.wantCount) + } + } + }, + ) + } +} + +func TestGetPAMProvider(t *testing.T) { + tests := []struct { + name string + providerId int + mockResponse interface{} + mockStatusCode int + wantErr bool + }{ + { + name: "successful get", + providerId: 1, + mockResponse: mockProviderResponseLegacy, + mockStatusCode: http.StatusOK, + wantErr: false, + }, + { + name: "not found", + providerId: 999, + mockResponse: map[string]string{"error": "not found"}, + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "server error", + providerId: 1, + mockResponse: map[string]string{"error": "internal server error"}, + mockStatusCode: http.StatusInternalServerError, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/1" + if tt.providerId == 999 { + expectedPath = "/KeyfactorAPI/PamProviders/999" + } + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "GET" { + t.Errorf("Expected GET method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.GetPAMProvider(tt.providerId) + if (err != nil) != tt.wantErr { + t.Errorf("GetPAMProvider() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if got.Id != mockProviderResponseLegacy.Id { + t.Errorf("GetPAMProvider() Id = %v, want %v", got.Id, mockProviderResponseLegacy.Id) + } + } + }, + ) + } +} + +func TestCreatePAMProvider(t *testing.T) { + createRequest := &ProviderCreateRequest{ + Name: "New PAM Provider", + Remote: false, + Area: 1, + ProviderType: ProviderType{ + Id: mockProviderTypeId, + Name: &mockProviderTypeName, + }, + SecuredAreaId: intPtr(1), + } + + tests := []struct { + name string + request *ProviderCreateRequest + mockResponse interface{} + mockStatusCode int + wantErr bool + }{ + { + name: "successful create", + request: createRequest, + mockResponse: mockProviderResponseLegacy, + mockStatusCode: http.StatusOK, + wantErr: false, + }, + { + name: "bad request - missing name", + request: &ProviderCreateRequest{}, + mockResponse: map[string]string{"error": "name is required"}, + mockStatusCode: http.StatusBadRequest, + wantErr: true, + }, + { + name: "server error", + request: createRequest, + mockResponse: map[string]string{"error": "internal server error"}, + mockStatusCode: http.StatusInternalServerError, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/KeyfactorAPI/PamProviders" { + t.Errorf("Expected path /KeyfactorAPI/PamProviders, got %s", r.URL.Path) + } + if r.Method != "POST" { + t.Errorf("Expected POST method, got %s", r.Method) + } + + // Verify request body + var receivedRequest ProviderCreateRequest + if err := json.NewDecoder(r.Body).Decode(&receivedRequest); err != nil { + t.Errorf("Failed to decode request body: %v", err) + } + if receivedRequest.Name != tt.request.Name { + t.Errorf("Expected name %s, got %s", tt.request.Name, receivedRequest.Name) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.CreatePAMProvider(tt.request) + if (err != nil) != tt.wantErr { + t.Errorf("CreatePAMProvider() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if got.Id != mockProviderResponseLegacy.Id { + t.Errorf("CreatePAMProvider() Id = %v, want %v", got.Id, mockProviderResponseLegacy.Id) + } + } + }, + ) + } +} + +func TestUpdatePAMProvider(t *testing.T) { + updateRequest := &ProviderUpdateRequestLegacy{ + Id: 1, + Name: "Updated PAM Provider", + Remote: false, + Area: 1, + ProviderType: ProviderType{ + Id: mockProviderTypeId, + Name: &mockProviderTypeName, + }, + SecuredAreaId: intPtr(1), + } + + updatedResponse := ProviderResponseLegacy{ + Id: 1, + Name: stringPtr("Updated PAM Provider"), + Area: 1, + ProviderType: &ProviderType{ + Id: mockProviderTypeId, + Name: &mockProviderTypeName, + }, + SecuredAreaId: intPtr(1), + Remote: false, + } + + tests := []struct { + name string + request *ProviderUpdateRequestLegacy + mockResponse interface{} + mockStatusCode int + wantErr bool + }{ + { + name: "successful update", + request: updateRequest, + mockResponse: updatedResponse, + mockStatusCode: http.StatusOK, + wantErr: false, + }, + { + name: "not found", + request: &ProviderUpdateRequestLegacy{ + Id: 999, + Name: "Nonexistent Provider", + }, + mockResponse: map[string]string{"error": "not found"}, + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "bad request", + request: &ProviderUpdateRequestLegacy{Id: 1}, + mockResponse: map[string]string{"error": "invalid request"}, + mockStatusCode: http.StatusBadRequest, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/KeyfactorAPI/PamProviders" { + t.Errorf("Expected path /KeyfactorAPI/PamProviders, got %s", r.URL.Path) + } + if r.Method != "PUT" { + t.Errorf("Expected PUT method, got %s", r.Method) + } + + // Verify request body + var receivedRequest ProviderUpdateRequestLegacy + if err := json.NewDecoder(r.Body).Decode(&receivedRequest); err != nil { + t.Errorf("Failed to decode request body: %v", err) + } + if receivedRequest.Id != tt.request.Id { + t.Errorf("Expected Id %d, got %d", tt.request.Id, receivedRequest.Id) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.UpdatePAMProvider(tt.request) + if (err != nil) != tt.wantErr { + t.Errorf("UpdatePAMProvider() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if *got.Name != "Updated PAM Provider" { + t.Errorf("UpdatePAMProvider() Name = %v, want %v", *got.Name, "Updated PAM Provider") + } + } + }, + ) + } +} + +func TestDeletePAMProvider(t *testing.T) { + tests := []struct { + name string + providerId int + mockStatusCode int + wantErr bool + }{ + { + name: "successful delete", + providerId: 1, + mockStatusCode: http.StatusNoContent, + wantErr: false, + }, + { + name: "not found", + providerId: 999, + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "provider in use", + providerId: 1, + mockStatusCode: http.StatusConflict, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/1" + if tt.providerId == 999 { + expectedPath = "/KeyfactorAPI/PamProviders/999" + } + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "DELETE" { + t.Errorf("Expected DELETE method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + err := client.DeletePAMProvider(tt.providerId) + if (err != nil) != tt.wantErr { + t.Errorf("DeletePAMProvider() error = %v, wantErr %v", err, tt.wantErr) + } + }, + ) + } +} + +func TestListLocalPAMEntries(t *testing.T) { + tests := []struct { + name string + providerId int + query *GetPAMProviderQuery + mockResponse interface{} + mockStatusCode int + wantErr bool + wantCount int + }{ + { + name: "successful list without query", + providerId: 1, + query: nil, + mockResponse: []LocalPAMEntryResponse{ + mockLocalPAMEntry, + }, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 1, + }, + { + name: "successful list with query", + providerId: 1, + query: &GetPAMProviderQuery{ + ReturnLimit: 10, + }, + mockResponse: []LocalPAMEntryResponse{ + mockLocalPAMEntry, + }, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 1, + }, + { + name: "empty list", + providerId: 1, + query: nil, + mockResponse: []LocalPAMEntryResponse{}, + mockStatusCode: http.StatusOK, + wantErr: false, + wantCount: 0, + }, + { + name: "provider not found", + providerId: 999, + query: nil, + mockResponse: map[string]string{"error": "provider not found"}, + mockStatusCode: http.StatusNotFound, + wantErr: true, + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/Local/1/Entries" + if tt.providerId == 999 { + expectedPath = "/KeyfactorAPI/PamProviders/Local/999/Entries" + } + if !strings.HasPrefix(r.URL.Path, expectedPath) { + t.Errorf("Expected path to start with %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "GET" { + t.Errorf("Expected GET method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.ListLocalPAMEntries(tt.providerId, tt.query) + if (err != nil) != tt.wantErr { + t.Errorf("ListLocalPAMEntries() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if len(*got) != tt.wantCount { + t.Errorf("ListLocalPAMEntries() got %d items, want %d", len(*got), tt.wantCount) + } + } + }, + ) + } +} + +func TestCreateLocalPAMEntry(t *testing.T) { + createRequest := &LocalPAMEntryCreateRequest{ + SecretName: "new-secret", + Description: stringPtr("New secret description"), + SecretValue: "super-secret-value", + } + + createdResponse := LocalPAMEntryResponse{ + ProviderId: 1, + SecretName: stringPtr("new-secret"), + Description: stringPtr("New secret description"), + } + + tests := []struct { + name string + providerId int + request *LocalPAMEntryCreateRequest + mockResponse interface{} + mockStatusCode int + wantErr bool + }{ + { + name: "successful create", + providerId: 1, + request: createRequest, + mockResponse: createdResponse, + mockStatusCode: http.StatusOK, + wantErr: false, + }, + { + name: "bad request - missing secret name", + providerId: 1, + request: &LocalPAMEntryCreateRequest{ + SecretValue: "value", + }, + mockResponse: map[string]string{"error": "secret name is required"}, + mockStatusCode: http.StatusBadRequest, + wantErr: true, + }, + { + name: "provider not found", + providerId: 999, + request: createRequest, + mockResponse: map[string]string{"error": "provider not found"}, + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "duplicate secret name", + providerId: 1, + request: createRequest, + mockResponse: map[string]string{"error": "secret already exists"}, + mockStatusCode: http.StatusConflict, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/Local/1/Entries" + if tt.providerId == 999 { + expectedPath = "/KeyfactorAPI/PamProviders/Local/999/Entries" + } + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "POST" { + t.Errorf("Expected POST method, got %s", r.Method) + } + + // Verify request body + var receivedRequest LocalPAMEntryCreateRequest + if err := json.NewDecoder(r.Body).Decode(&receivedRequest); err != nil { + t.Errorf("Failed to decode request body: %v", err) + } + if receivedRequest.SecretName != tt.request.SecretName { + t.Errorf( + "Expected SecretName %s, got %s", + tt.request.SecretName, + receivedRequest.SecretName, + ) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.CreateLocalPAMEntry(tt.providerId, tt.request) + if (err != nil) != tt.wantErr { + t.Errorf("CreateLocalPAMEntry() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if *got.SecretName != "new-secret" { + t.Errorf("CreateLocalPAMEntry() SecretName = %v, want %v", *got.SecretName, "new-secret") + } + } + }, + ) + } +} + +func TestUpdateLocalPAMEntry(t *testing.T) { + updateRequest := &LocalPAMEntryUpdateRequest{ + SecretName: "updated-secret", + Description: stringPtr("Updated description"), + SecretValue: stringPtr("updated-value"), + } + + updatedResponse := LocalPAMEntryResponse{ + ProviderId: 1, + SecretName: stringPtr("updated-secret"), + Description: stringPtr("Updated description"), + } + + tests := []struct { + name string + providerId int + secretName string + request *LocalPAMEntryUpdateRequest + mockResponse interface{} + mockStatusCode int + wantErr bool + }{ + { + name: "successful update", + providerId: 1, + secretName: "test-secret", + request: updateRequest, + mockResponse: updatedResponse, + mockStatusCode: http.StatusOK, + wantErr: false, + }, + { + name: "secret not found", + providerId: 1, + secretName: "nonexistent", + request: &LocalPAMEntryUpdateRequest{ + SecretName: "nonexistent", + }, + mockResponse: map[string]string{"error": "secret not found"}, + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "provider not found", + providerId: 999, + secretName: "test-secret", + request: updateRequest, + mockResponse: map[string]string{"error": "provider not found"}, + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/Local/1/Entries/test-secret" + if tt.providerId == 999 { + expectedPath = "/KeyfactorAPI/PamProviders/Local/999/Entries/test-secret" + } + if tt.secretName == "nonexistent" { + expectedPath = "/KeyfactorAPI/PamProviders/Local/1/Entries/nonexistent" + } + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "PUT" { + t.Errorf("Expected PUT method, got %s", r.Method) + } + + // Verify request body + var receivedRequest LocalPAMEntryUpdateRequest + if err := json.NewDecoder(r.Body).Decode(&receivedRequest); err != nil { + t.Errorf("Failed to decode request body: %v", err) + } + if receivedRequest.SecretName != tt.request.SecretName { + t.Errorf( + "Expected SecretName %s, got %s", + tt.request.SecretName, + receivedRequest.SecretName, + ) + } + + w.WriteHeader(tt.mockStatusCode) + _ = json.NewEncoder(w).Encode(tt.mockResponse) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + got, err := client.UpdateLocalPAMEntry(tt.providerId, tt.secretName, tt.request) + if (err != nil) != tt.wantErr { + t.Errorf("UpdateLocalPAMEntry() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != nil { + if *got.SecretName != "updated-secret" { + t.Errorf("UpdateLocalPAMEntry() SecretName = %v, want %v", *got.SecretName, "updated-secret") + } + } + }, + ) + } +} + +func TestDeleteLocalPAMEntry(t *testing.T) { + tests := []struct { + name string + providerId int + secretName string + mockStatusCode int + wantErr bool + }{ + { + name: "successful delete", + providerId: 1, + secretName: "test-secret", + mockStatusCode: http.StatusNoContent, + wantErr: false, + }, + { + name: "secret not found", + providerId: 1, + secretName: "nonexistent", + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "provider not found", + providerId: 999, + secretName: "test-secret", + mockStatusCode: http.StatusNotFound, + wantErr: true, + }, + { + name: "secret in use", + providerId: 1, + secretName: "in-use-secret", + mockStatusCode: http.StatusConflict, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + server := httptest.NewTLSServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/KeyfactorAPI/PamProviders/Local/1/Entries/test-secret" + if tt.providerId == 999 { + expectedPath = "/KeyfactorAPI/PamProviders/Local/999/Entries/test-secret" + } + if tt.secretName == "nonexistent" { + expectedPath = "/KeyfactorAPI/PamProviders/Local/1/Entries/nonexistent" + } + if tt.secretName == "in-use-secret" { + expectedPath = "/KeyfactorAPI/PamProviders/Local/1/Entries/in-use-secret" + } + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + if r.Method != "DELETE" { + t.Errorf("Expected DELETE method, got %s", r.Method) + } + + w.WriteHeader(tt.mockStatusCode) + }, + ), + ) + defer server.Close() + + client := newTestClient(server) + + err := client.DeleteLocalPAMEntry(tt.providerId, tt.secretName) + if (err != nil) != tt.wantErr { + t.Errorf("DeleteLocalPAMEntry() error = %v, wantErr %v", err, tt.wantErr) + } + }, + ) + } +} From 16e971f4833ccf2ecb9580ab79359e33fbedd58b Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:08:21 -0800 Subject: [PATCH 23/29] fix(api/models): PAM `ProviderType` models `Name` to use string rather than pointer --- v3/api/pam_test.go | 6 +++--- v3/api/pam_types.go | 2 +- v3/api/pam_types_models.go | 10 +++++----- v3/api/pam_types_test.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/v3/api/pam_test.go b/v3/api/pam_test.go index a199714..5603a97 100644 --- a/v3/api/pam_test.go +++ b/v3/api/pam_test.go @@ -190,7 +190,7 @@ func TestCreatePAMProvider(t *testing.T) { Area: 1, ProviderType: ProviderType{ Id: mockProviderTypeId, - Name: &mockProviderTypeName, + Name: mockProviderTypeName, }, SecuredAreaId: intPtr(1), } @@ -279,7 +279,7 @@ func TestUpdatePAMProvider(t *testing.T) { Area: 1, ProviderType: ProviderType{ Id: mockProviderTypeId, - Name: &mockProviderTypeName, + Name: mockProviderTypeName, }, SecuredAreaId: intPtr(1), } @@ -290,7 +290,7 @@ func TestUpdatePAMProvider(t *testing.T) { Area: 1, ProviderType: &ProviderType{ Id: mockProviderTypeId, - Name: &mockProviderTypeName, + Name: mockProviderTypeName, }, SecuredAreaId: intPtr(1), Remote: false, diff --git a/v3/api/pam_types.go b/v3/api/pam_types.go index 3aa78f7..ad7348c 100644 --- a/v3/api/pam_types.go +++ b/v3/api/pam_types.go @@ -60,7 +60,7 @@ func (c *Client) GetPAMProviderTypeByName(name string) (*ProviderTypeResponse, e // find the provider type with the matching name for _, t := range *types { - if t.Name != nil && *t.Name == name { + if t.Name == name { return &t, nil } } diff --git a/v3/api/pam_types_models.go b/v3/api/pam_types_models.go index d165df0..bcbe0f8 100644 --- a/v3/api/pam_types_models.go +++ b/v3/api/pam_types_models.go @@ -30,7 +30,7 @@ type SecretType int // ProviderTypeParameterResponse represents a parameter for a PAM provider type type ProviderTypeParameterResponse struct { Id int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` + Name string `json:"Name,omitempty"` DisplayName *string `json:"DisplayName,omitempty"` DataType PamParameterDataType `json:"DataType,omitempty"` InstanceLevel bool `json:"InstanceLevel,omitempty"` @@ -39,7 +39,7 @@ type ProviderTypeParameterResponse struct { // ProviderTypeResponse represents a PAM provider type type ProviderTypeResponse struct { Id string `json:"Id,omitempty"` // UUID format - Name *string `json:"Name,omitempty"` + Name string `json:"Name,omitempty"` Parameters *[]ProviderTypeParameterResponse `json:"Parameters,omitempty"` } @@ -60,7 +60,7 @@ type ProviderTypeCreateRequest struct { // ProviderCreateRequestProviderTypeParam represents a provider type parameter in a provider creation request type ProviderCreateRequestProviderTypeParam struct { Id int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` + Name string `json:"Name,omitempty"` DisplayName *string `json:"DisplayName,omitempty"` InstanceLevel bool `json:"InstanceLevel,omitempty"` } @@ -68,7 +68,7 @@ type ProviderCreateRequestProviderTypeParam struct { // PamProviderTypeParam represents a provider type parameter (full model) for PAM operations type PamProviderTypeParam struct { Id int `json:"Id,omitempty"` - Name *string `json:"Name,omitempty"` + Name string `json:"Name,omitempty"` DisplayName *string `json:"DisplayName,omitempty"` DataType PamParameterDataType `json:"DataType,omitempty"` InstanceLevel bool `json:"InstanceLevel,omitempty"` @@ -78,7 +78,7 @@ type PamProviderTypeParam struct { // ProviderType represents a PAM provider type (full model) type ProviderType struct { Id string `json:"Id,omitempty"` // UUID format - Name *string `json:"Name,omitempty"` + Name string `json:"Name,omitempty"` ProviderTypeParams *[]PamProviderTypeParam `json:"ProviderTypeParams,omitempty"` } diff --git a/v3/api/pam_types_test.go b/v3/api/pam_types_test.go index eac814a..da664aa 100644 --- a/v3/api/pam_types_test.go +++ b/v3/api/pam_types_test.go @@ -87,7 +87,7 @@ var ( Area: 1, ProviderType: &ProviderType{ Id: mockProviderTypeId, - Name: &mockProviderTypeName, + Name: mockProviderTypeName, }, SecuredAreaId: intPtr(1), Remote: false, From 7537550756191ecf79a9eaa89dc919634ba7e575 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:58:43 -0800 Subject: [PATCH 24/29] fix(api/pam): Add `GetPamProviderByName` helper function --- v3/api/pam.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/v3/api/pam.go b/v3/api/pam.go index bb6fb49..37fcd09 100644 --- a/v3/api/pam.go +++ b/v3/api/pam.go @@ -45,6 +45,24 @@ func (c *Client) ListPAMProviders(query *GetPAMProviderQuery) (*[]ProviderRespon return &jsonResp, nil } +// GetPamProviderByName returns a specific PAM provider by name +func (c *Client) GetPamProviderByName(name string) (*ProviderResponseLegacy, error) { + log.Printf("[INFO] Getting PAM provider with name: %s", name) + + query := &GetPAMProviderQuery{ + QueryString: fmt.Sprintf("Name eq '%s'", name), + } + providers, err := c.ListPAMProviders(query) + if err != nil { + return nil, err + } + + if providers == nil || len(*providers) == 0 { + return nil, fmt.Errorf("PAM provider with name '%s' not found", name) + } + return &(*providers)[0], nil +} + // GetPAMProvider returns a specific PAM provider by ID func (c *Client) GetPAMProvider(id int) (*ProviderResponseLegacy, error) { log.Printf("[INFO] Getting PAM provider with ID: %d", id) From 6b3d479cb56e672b0b6bede7a66c9165790ffeb9 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:30:38 -0800 Subject: [PATCH 25/29] fix(api/stores): Use same `StorePasswordConfig` for create and update store models --- v3/api/store_models.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/v3/api/store_models.go b/v3/api/store_models.go index bf4db2c..c466842 100644 --- a/v3/api/store_models.go +++ b/v3/api/store_models.go @@ -51,14 +51,14 @@ type UpdateStoreFctArgs struct { // automatically populated by the CreateStore method. However, if configured, this field will be used. PropertiesString string `json:"Properties,omitempty"` // Mapped name-value pair field used to configure properties. - Properties map[string]interface{} `json:"-"` - AgentId string `json:"AgentId"` - AgentAssigned *bool `json:"AgentAssigned,omitempty"` - ContainerName *string `json:"ContainerName,omitempty"` - InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` - ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` - SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` - Password *UpdateStorePasswordConfig `json:"Password"` + Properties map[string]interface{} `json:"-"` + AgentId string `json:"AgentId"` + AgentAssigned *bool `json:"AgentAssigned,omitempty"` + ContainerName *string `json:"ContainerName,omitempty"` + InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` + ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` + SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` + Password *StorePasswordConfig `json:"Password"` } type UpdateStorePasswordConfig struct { @@ -102,14 +102,14 @@ type ReEnrollmnentConfig struct { // StorePasswordConfig configures the password field for a new certificate store. // TODO: make re-usable struct for Secret type fields type StorePasswordConfig struct { - Value *string `json:"SecretValue"` - SecretTypeGuid *string `json:"SecretTypeGuid,omitempty"` - InstanceId *string `json:"InstanceId,omitempty"` - InstanceGuid *string `json:"InstanceGuid,omitempty"` - ProvidererTypeParameterValues *[]ProviderTypeParameterValue `json:"ProviderTypeParameterValues"` - ProviderId int `json:"ProviderId"` - IsManaged bool `json:"IsManaged"` - HasValue bool `json:"HasValue"` + Value *string `json:"SecretValue"` + SecretTypeGuid *string `json:"SecretTypeGuid,omitempty"` + InstanceId *string `json:"InstanceId,omitempty"` + InstanceGuid *string `json:"InstanceGuid,omitempty"` + ProviderTypeParameterValues *[]ProviderTypeParameterValue `json:"ProviderTypeParameterValues"` + ProviderId int `json:"ProviderId"` + IsManaged bool `json:"IsManaged"` + HasValue bool `json:"HasValue"` } // ProviderTypeParameterValues - Not yet implemented // ProviderTypeParameterValues ProviderTypeParams - Not implemented From ba189ad8fc429e37565ce0641ea6c2f00f742a68 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:05:52 -0800 Subject: [PATCH 26/29] fix(models/stores): Use `UpdateStorePasswordConfig` on PUT and POST payloads. feat(models/stores): Add `RemoteProviderName` to `StorePasswordConfig` Signed-off-by: spbsoluble <1661003+spbsoluble@users.noreply.github.com> --- v3/api/store_models.go | 59 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/v3/api/store_models.go b/v3/api/store_models.go index c466842..6cbb38c 100644 --- a/v3/api/store_models.go +++ b/v3/api/store_models.go @@ -27,14 +27,14 @@ type CreateStoreFctArgs struct { // automatically populated by the CreateStore method. However, if configured, this field will be used. PropertiesString string `json:"Properties,omitempty"` // Mapped name-value pair field used to configure properties. - Properties map[string]interface{} `json:"-"` - AgentId string `json:"AgentId"` - AgentAssigned *bool `json:"AgentAssigned,omitempty"` - ContainerName *string `json:"ContainerName,omitempty"` - InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` - ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` - SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` - Password *StorePasswordConfig `json:"Password"` + Properties map[string]interface{} `json:"-"` + AgentId string `json:"AgentId"` + AgentAssigned *bool `json:"AgentAssigned,omitempty"` + ContainerName *string `json:"ContainerName,omitempty"` + InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` + ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` + SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` + Password *UpdateStorePasswordConfig `json:"Password"` } // UpdateStoreFctArgs holds the function arguments used for calling the UpdateStore method. @@ -51,20 +51,21 @@ type UpdateStoreFctArgs struct { // automatically populated by the CreateStore method. However, if configured, this field will be used. PropertiesString string `json:"Properties,omitempty"` // Mapped name-value pair field used to configure properties. - Properties map[string]interface{} `json:"-"` - AgentId string `json:"AgentId"` - AgentAssigned *bool `json:"AgentAssigned,omitempty"` - ContainerName *string `json:"ContainerName,omitempty"` - InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` - ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` - SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` - Password *StorePasswordConfig `json:"Password"` + Properties map[string]interface{} `json:"-"` + AgentId string `json:"AgentId"` + AgentAssigned *bool `json:"AgentAssigned,omitempty"` + ContainerName *string `json:"ContainerName,omitempty"` + InventorySchedule *InventorySchedule `json:"InventorySchedule,omitempty"` + ReEnrollmentStatus *ReEnrollmnentConfig `json:"ReEnrollmentStatus,omitempty"` + SetNewPasswordAllowed *bool `json:"SetNewPasswordAllowed,omitempty"` + Password *UpdateStorePasswordConfig `json:"Password"` } type UpdateStorePasswordConfig struct { - SecretValue *string `json:"SecretValue"` // used for setting kf-secret value or No Value (null) - Parameters map[string]string `json:"Parameters"` - Provider int `json:"Provider"` + SecretValue *string `json:"SecretValue,omitempty"` // used for setting kf-secret value or No Value ( + // null) + Parameters map[string]string `json:"Parameters,omitempty"` // used for setting PAM parameters + Provider int `json:"Provider"` // used for setting PAM provider ID } // InventorySchedule holds configuration data for creating an inventory schedule for a certificate store in Keyfactor @@ -102,27 +103,27 @@ type ReEnrollmnentConfig struct { // StorePasswordConfig configures the password field for a new certificate store. // TODO: make re-usable struct for Secret type fields type StorePasswordConfig struct { - Value *string `json:"SecretValue"` + Value *string `json:"Value,omitempty"` // TODO: In a GET response this is just `Value`, but in a POST/PUT this is `SecretValue` + TypedValue *string `json:"TypedValue,omitempty"` SecretTypeGuid *string `json:"SecretTypeGuid,omitempty"` InstanceId *string `json:"InstanceId,omitempty"` InstanceGuid *string `json:"InstanceGuid,omitempty"` - ProviderTypeParameterValues *[]ProviderTypeParameterValue `json:"ProviderTypeParameterValues"` + ProviderTypeParameterValues *[]ProviderTypeParameterValue `json:"ProviderTypeParameterValues,omitempty"` ProviderId int `json:"ProviderId"` IsManaged bool `json:"IsManaged"` HasValue bool `json:"HasValue"` -} // ProviderTypeParameterValues - Not yet implemented -// ProviderTypeParameterValues ProviderTypeParams - Not implemented - -/* Future non-critical functionality */ + RemoteProviderName *string `json:"RemoteProviderName,omitempty"` +} +// ProviderTypeParameterValue - Not yet implemented type ProviderTypeParameterValue struct { Id int `json:"Id"` - Value *string `json:"Value"` + Value *string `json:"Value,omitempty"` ParameterId int `json:"ParameterId"` // defaults always to 0, likely deprecated InstanceId *string `json:"InstanceId"` // defaults null, likely deprecated - InstanceGuid *string `json:"InstanceGuid"` - Provider *string `json:"Provider"` // defaults null, likely deprecated - ProviderTypeParam ProviderTypeParam `json:"ProviderTypeParam"` + InstanceGuid *string `json:"InstanceGuid,omitempty"` + Provider *Provider `json:"Provider,omitempty"` // defaults null, likely deprecated + ProviderTypeParam ProviderTypeParam `json:"ProviderTypeParam,omitempty"` } type ProviderTypeParam struct { From d789bbfcefc6ebe1f599a48e8e824b4bb54a65e9 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:07:40 -0800 Subject: [PATCH 27/29] feat(stores): If unable to deserialize `GetCertificateStoreResponse` return raw JSON response in error if possible. Signed-off-by: spbsoluble <1661003+spbsoluble@users.noreply.github.com> --- v3/api/store.go | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/v3/api/store.go b/v3/api/store.go index 1e37c73..7ff31b1 100644 --- a/v3/api/store.go +++ b/v3/api/store.go @@ -18,6 +18,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "log" "net/http" "strconv" @@ -66,9 +67,9 @@ func (c *Client) CreateStore(ca *CreateStoreFctArgs) (*CreateStoreResponse, erro Payload: &ca, } - resp, err := c.sendRequest(keyfactorAPIStruct) - if err != nil { - return nil, err + resp, respErr := c.sendRequest(keyfactorAPIStruct) + if respErr != nil { + return nil, respErr } jsonResp := &CreateStoreResponse{} @@ -274,17 +275,26 @@ func (c *Client) GetCertificateStoreByID(storeId string) (*GetCertificateStoreRe if err != nil { return nil, err } + defer resp.Body.Close() + + bodyBytes, readErr := io.ReadAll(resp.Body) + if readErr != nil { + return nil, readErr + } jsonResp := &GetCertificateStoreResponse{} - err = json.NewDecoder(resp.Body).Decode(&jsonResp) - if err != nil { - return nil, err + if jErr := json.Unmarshal(bodyBytes, &jsonResp); jErr != nil { + rawJson := make(map[string]interface{}) + if mErr := json.Unmarshal(bodyBytes, &rawJson); mErr != nil { + return nil, fmt.Errorf("error decoding response: %v", mErr) + } + return nil, fmt.Errorf("error decoding response: %v, raw response: %v", jErr, rawJson) } jsonResp.Properties = unmarshalPropertiesString(jsonResp.PropertiesString) return jsonResp, nil } -// GetCertificateStoreByID takes arguments for a certificate store ID to facilitate a call to Keyfactor +// GetCertificateStoreByContainerID takes arguments for a certificate store ID to facilitate a call to Keyfactor // that retrieves a certificate store context. Only the store ID is required. A pointer to a GetStoreByIDResp struct // is returned that contains information on the certificate store. func (c *Client) GetCertificateStoreByContainerID(containerID interface{}) (*[]GetCertificateStoreResponse, error) { @@ -647,3 +657,16 @@ func buildPropertiesInterface(properties map[string]string) interface{} { return propertiesInterface } + +func mapToEscapedJSONString(m map[string]interface{}) (string, error) { + // Convert the map to a byte slice of JSON + jsonBytes, err := json.Marshal(m) + if err != nil { + return "", err + } + + // Escape any special characters in the JSON string + escapedString := string(jsonBytes) + + return escapedString, nil +} From 3826688c35ae0f64da8ca86a8beb95a339492c65 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:10:45 -0800 Subject: [PATCH 28/29] chore(helpers): Move helpers related to unpacking API responses to API library --- v3/api/certificate.go | 7 +- v3/api/helpers.go | 277 ++++++++++++++++++++++++++++++++++++++++++ v3/go.mod | 1 + v3/go.sum | 2 + 4 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 v3/api/helpers.go diff --git a/v3/api/certificate.go b/v3/api/certificate.go index bd01c40..b2ac95e 100644 --- a/v3/api/certificate.go +++ b/v3/api/certificate.go @@ -784,10 +784,13 @@ func (c *Client) RecoverCertificate( } log.Println("[DEBUG] RecoverCertificate: Decoding PFX chain") - priv, leaf, chain, pErr := pkcs12.DecodeChain(pfxDer, rca.Password) + priv, leaf, chain, pErr := pkcs12.DecodeChain( + pfxDer, + rca.Password, + ) // TODO: Attempt to parse as PKCS12 because that used to be the "default" export format. if pErr != nil { log.Println("[ERROR] RecoverCertificate: Error decoding PFX chain", pErr.Error()) - return nil, nil, nil, &jsonResp.PFX, pErr + return nil, nil, nil, &jsonResp.PFX, nil //TODO: Don't return error because it's probably actually a PEM } log.Println("[INFO] Recovered certificate successfully") diff --git a/v3/api/helpers.go b/v3/api/helpers.go new file mode 100644 index 0000000..94f4dd7 --- /dev/null +++ b/v3/api/helpers.go @@ -0,0 +1,277 @@ +package api + +import ( + `crypto/ecdsa` + `crypto/sha1` + `crypto/x509` + `encoding/base64` + `encoding/hex` + `encoding/pem` + `fmt` + `reflect` + "crypto/rsa" + "crypto/ed25519" + `github.com/spbsoluble/go-pkcs12` + "github.com/youmark/pkcs8" +) + +// UnpackPEM extracts the private key, certificate, and CA certificates from PEM-formatted data. +// If the private key is encrypted (PKCS#8 encrypted format), it will be decrypted using the provided password. +// +// Parameters: +// - pemData: The PEM data as a string, *string, or []byte (may be base64-encoded) +// - password: The password used for decrypting the encrypted private key +// +// Returns: +// - privateKey: The decrypted private key in PEM format +// - certificate: The leaf certificate in PEM format +// - caCertificates: A slice of CA certificates in PEM format (if any) +// - err: An error that describes why the unpacking failed, if any +func UnpackPEM(pemData interface{}, password string) ( + privateKey, certificate string, + caCertificates []string, + err error, +) { + var pemBytes []byte + + // Convert pemData to []byte + switch v := pemData.(type) { + case string: + pemBytes = []byte(v) + // Try base64 decode + if decoded, decodeErr := base64.StdEncoding.DecodeString(v); decodeErr == nil && len(decoded) > 0 { + pemBytes = decoded + } + case *string: + if v == nil { + err = fmt.Errorf("pemData pointer is nil") + return + } + pemBytes = []byte(*v) + // Try base64 decode + if decoded, decodeErr := base64.StdEncoding.DecodeString(*v); decodeErr == nil && len(decoded) > 0 { + pemBytes = decoded + } + case []byte: + pemBytes = v + // Try base64 decode + if decoded, decodeErr := base64.StdEncoding.DecodeString(string(v)); decodeErr == nil && len(decoded) > 0 { + pemBytes = decoded + } + default: + err = fmt.Errorf("invalid pemData type: expected string, *string, or []byte, got %T", pemData) + return + } + + var certificates []string + var encryptedKeyBlock *pem.Block + + // Parse all PEM blocks + remaining := pemBytes + for { + var block *pem.Block + block, remaining = pem.Decode(remaining) + if block == nil { + break + } + + switch block.Type { + case "CERTIFICATE": + certPEM := string(pem.EncodeToMemory(block)) + certificates = append(certificates, certPEM) + case "ENCRYPTED PRIVATE KEY": + encryptedKeyBlock = block + case "RSA PRIVATE KEY", "EC PRIVATE KEY", "PRIVATE KEY": + // Already unencrypted + privateKey = string(pem.EncodeToMemory(block)) + } + } + + // If we found an encrypted private key, decrypt it + if encryptedKeyBlock != nil && privateKey == "" { + decryptedKey, decryptErr := DecryptPKCS8PrivateKey(encryptedKeyBlock.Bytes, password) + if decryptErr != nil { + err = fmt.Errorf("failed to decrypt private key: %v", decryptErr) + return + } + privateKey = decryptedKey + } + + // Assign certificates: first is leaf, rest are CA chain + if len(certificates) > 0 { + certificate = certificates[0] + if len(certificates) > 1 { + caCertificates = certificates[1:] + } + } + + return privateKey, certificate, caCertificates, nil +} + +// DecryptPKCS8PrivateKey decrypts a PKCS#8 encrypted private key and returns it in PEM format. +// Uses the github.com/youmark/pkcs8 package which supports PBES2 encryption schemes +// including AES-128-CBC, AES-192-CBC, AES-256-CBC, AES-128-GCM, AES-192-GCM, AES-256-GCM. +func DecryptPKCS8PrivateKey(encryptedKey []byte, password string) (string, error) { + // Use the pkcs8 package to parse and decrypt the encrypted PKCS#8 key + // This handles PBES2 encryption with various algorithms + parsedKey, err := pkcs8.ParsePKCS8PrivateKey(encryptedKey, []byte(password)) + if err != nil { + return "", fmt.Errorf("failed to decrypt PKCS#8 key: %v", err) + } + + // Encode the decrypted key back to PEM format + pemBlock, encodeErr := EncodePrivateKey(parsedKey) + if encodeErr != nil { + return "", fmt.Errorf("failed to encode decrypted key: %v", encodeErr) + } + + return string(pem.EncodeToMemory(pemBlock)), nil +} + +// UnpackPkcs12 extracts the private key, certificate, and CA certificates from a PKCS#12/PFX file. +// Parameters: +// - pfxData: The byte slice containing the PKCS#12/PFX file data. +// - password: The password used for decrypting the PKCS#12/PFX file. +// +// Returns: +// - privateKey: The private key extracted from the PFX file, in PEM format. +// - certificate: The certificate extracted from the PFX file, in PEM format. +// - caCertificates: A slice of CA certificates extracted from the PFX file, in PEM format (if any). +// - err: An error that describes why the unpacking failed, if any. +func UnpackPkcs12(pfxData interface{}, password string) ( + privateKey, certificate string, + caCertificates []string, + err error, +) { + // Convert pfxData to []byte, if necessary + var pfxBytes []byte + + switch v := pfxData.(type) { + case string: + // attempt to base64 decode first + pfxBytes = []byte(v) // Convert string to []byte + decoded, decodeErr := base64.StdEncoding.DecodeString(v) + if decodeErr == nil && len(decoded) > 0 { + pfxBytes = decoded + break + } + + case *string: + if v == nil { + err = fmt.Errorf("pfxData pointer is nil") + return + } + // attempt to base64 decode first + pfxBytes = []byte(*v) // Convert *string to []byte + decoded, decodeErr := base64.StdEncoding.DecodeString(*v) + if decodeErr == nil && len(decoded) > 0 { + pfxBytes = decoded + break + } + break + case []byte: + pfxBytes = v + default: + err = fmt.Errorf( + "invalid pfxData type: expected string or []byte, got %s (type %T)", + reflect.ValueOf(pfxData), + pfxData, + ) + return + } + + // Decode the PKCS#12 data + parsedKey, parsedCert, parsedCAs, pkcs12Err := pkcs12.DecodeChain(pfxBytes, password) + if pkcs12Err != nil { + err = fmt.Errorf("failed to decode PKCS#12 data: %v", pkcs12Err) + return + } + + // PEM-encode the private key + privateKeyBlock, keyErr := EncodePrivateKey(parsedKey) + if keyErr != nil { + err = fmt.Errorf("failed to encode private key: %v", keyErr) + return + } + privateKey = string(pem.EncodeToMemory(privateKeyBlock)) + + // PEM-encode the certificate + certificateBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: parsedCert.Raw, + } + certificate = string(pem.EncodeToMemory(certificateBlock)) + + // PEM-encode the CA certificates (if any) + for _, caCert := range parsedCAs { + caCertBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: caCert.Raw, + } + caCertificates = append(caCertificates, string(pem.EncodeToMemory(caCertBlock))) + } + + return privateKey, certificate, caCertificates, nil +} + +// EncodePrivateKey determines the type of private key (RSA or ECDSA) and encodes it as a PEM block. +// Parameters: +// - key: The private key to encode. +// +// Returns: +// - pemBlock: The PEM block representation of the private key. +// - err: An error if the private key type is unsupported or invalid. +func EncodePrivateKey(key interface{}) (*pem.Block, error) { + switch k := key.(type) { + case *rsa.PrivateKey: + return &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(k), + }, nil + case *ecdsa.PrivateKey: + encodedKey, err := x509.MarshalECPrivateKey(k) + if err != nil { + return nil, fmt.Errorf("failed to encode ECDSA private key: %v", err) + } + return &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: encodedKey, + }, nil + case ed25519.PrivateKey: + return &pem.Block{ + Type: "PRIVATE KEY", + Bytes: k, + }, nil + default: + return nil, fmt.Errorf("unsupported private key type: %T", key) + } +} + +// GetCertificateThumbprint computes the thumbprint (SHA-1 hash) of an x509 certificate. +// +// The thumbprint is calculated by hashing the raw DER-encoded certificate data +// using the SHA-1 algorithm. +// +// Parameters: +// - cert: A pointer to an x509.Certificate object. +// +// Returns: +// - A string representing the hexadecimal-encoded thumbprint of the certificate. +// - An error, which will be nil if the computation succeeds. +// +// Example: +// +// thumbprint, err := GetCertificateThumbprint(cert) +// if err != nil { +// log.Fatalf("error computing thumbprint: %v", err) +// } +// fmt.Println("Certificate Thumbprint:", thumbprint) +func GetCertificateThumbprint(cert *x509.Certificate) (string, error) { + // Compute the SHA-1 hash of the certificate's raw DER data + hash := sha1.Sum(cert.Raw) + + // Convert the hash to a hexadecimal string + thumbprint := hex.EncodeToString(hash[:]) + + return thumbprint, nil +} diff --git a/v3/go.mod b/v3/go.mod index 70dc405..2cd90a7 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -22,6 +22,7 @@ require ( github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/spbsoluble/go-pkcs12 v0.3.3 + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 go.mozilla.org/pkcs7 v0.9.0 ) diff --git a/v3/go.sum b/v3/go.sum index ede870e..0a2509a 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -62,6 +62,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= From 1f01c2b470f8b6ea3c82363d957d71979d10bae2 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:13:29 -0800 Subject: [PATCH 29/29] feat(helpers): Add common helpers around decoding certificate API responses. --- v3/api/helpers.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/v3/api/helpers.go b/v3/api/helpers.go index 94f4dd7..8a2d460 100644 --- a/v3/api/helpers.go +++ b/v3/api/helpers.go @@ -1,18 +1,18 @@ package api import ( - `crypto/ecdsa` - `crypto/sha1` - `crypto/x509` - `encoding/base64` - `encoding/hex` - `encoding/pem` - `fmt` - `reflect` - "crypto/rsa" + "crypto/ecdsa" "crypto/ed25519" - `github.com/spbsoluble/go-pkcs12` + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/pem" + "fmt" + "github.com/spbsoluble/go-pkcs12" "github.com/youmark/pkcs8" + "reflect" ) // UnpackPEM extracts the private key, certificate, and CA certificates from PEM-formatted data.