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 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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 07/13] 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 08/13] 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 09/13] 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 10/13] 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 11/13] 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 12/13] 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 13/13] 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.