Compare commits

...
Author SHA1 Message Date
Todd Knight f2d3615a94 Validate host sets when they are created or updated. 2021-11-18 16:52:32 -07:00
ToddandJeff Mitchell 6ac09ab032 README includes data regarding required disabled credential rotation (#4)
* Update Readme with details around credential rotation needing to be disabled.

* Apply suggestions from code review

Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
2021-11-18 10:01:30 -07:00
Todd d9fe0aaacb Force disable_credential_rotation to true. Validate catalog and secrets input (#3)
* Remove rotation logic.  Validate input.
2021-11-16 16:06:33 -07:00
Jeff Mitchell 24a9c0b090 Update SDK 2021-11-12 19:48:40 -05:00
Todd Knight 3aff7f5aba Return an empty response instead of nil, nil. Remove unimplemented calls so pb.UnimplementedHostPluginServiceServer handles the requests. 2021-11-10 15:26:19 -07:00
Jeff Mitchell 7197294c2c Update SDK dep 2021-11-10 15:13:26 -05:00
Jeff Mitchell fe1b80eadc Update deps 2021-11-10 15:06:15 -05:00
Jeff Mitchell 933fc23905 Massive cleanup. Tests are passing. 2021-11-10 13:59:21 -05:00
Todd Knight 073c3e1904 Update boundary sdk dependency. 2021-11-10 11:04:58 -07:00
Todd 22b62c202f Update boundary/sdk dependency due to breaking proto change. (#2) 2021-10-29 13:56:34 -07:00
8 changed files with 746 additions and 536 deletions

No files matched your search

+12 -14
View File
@@ -19,24 +19,22 @@ Current limitations of this plugin:
compute resource
- A client secret must be generated against a registered application with any
necessary permissions
- For now, credential rotation must be disabled using the `disable_credential_rotation`
attribute. There is some additional work that the Boundary team identified
that is necessary to make this a seamless experience.
## Credential Rotation
Although credentials are stored encrypted within Boundary, by default this
plugin will attempt to rotate credentials when they are supplied through the
`secrets` object on a create or update call to the host catalog resource. The
given credentials will be used to create a new credential, and then the given
credential will be revoked. In this way, after rotation, only Boundary knows the
client secret in use by this plugin.
*NOTE*: For now credential rotation must be explicitly disabled by setting
`disable_credential_rotation` to true on the host catalog. When we lift this
restriction Boundary will manage the rotation of credentials for you.
*NOTE*: Azure's credentials are eventually consistent and there may be
significant delay (up to ten minutes or more) between when a credential is
created and is available for use, or when a credential is deleted and
unavailable for use. At the API level, this can be true even if the Azure Portal
indicates that the credential is (or isn't) there. This delay will manifest as
authentication failures when performing actions using this plugin. If secrets
have just been supplied and `disable_credential_rotation` is not specified,
please be patient and try again later.
Although credentials are stored encrypted within Boundary, when credential
rotation isn't explicitly disabled, this plugin will attempt to rotate
credentials when they are supplied through the `secrets` object on a create or
update call to the host catalog resource. The given credentials will be used to
create a new credential, and then the given credential will be revoked. In this
way, after rotation, only Boundary knows the client secret in use by this plugin.
## Attributes and Secrets
+55 -9
View File
@@ -1,17 +1,63 @@
module github.com/hashicorp/boundary-plugin-host-azure
go 1.16
go 1.17
require (
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.21
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/hashicorp/boundary/sdk v0.0.12-0.20211014161407-f01a8060a066
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1
github.com/Azure/azure-sdk-for-go v59.2.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.22
github.com/Azure/go-autorest/autorest/azure/auth v0.5.9
github.com/hashicorp/boundary/sdk v0.0.12-0.20211112235128-46a1edcb3b7b
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2
github.com/kr/pretty v0.3.0
github.com/manicminer/hamilton v0.32.0
github.com/manicminer/hamilton v0.33.0
github.com/mitchellh/mapstructure v1.4.2
github.com/stretchr/testify v1.7.0
google.golang.org/protobuf v1.27.1
)
require (
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/color v1.12.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/eventlogger v0.1.1-0.20211106154408-4ff8da3a890c // indirect
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v0.16.2 // indirect
github.com/hashicorp/go-kms-wrapping v0.6.6 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.6.2 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6 // indirect
google.golang.org/grpc v1.40.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
+22 -19
View File
@@ -34,28 +34,28 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible h1:Cw16jiP4dI+CK761aq44ol4RV5dUiIIXky1+EKpoiVM=
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v59.2.0+incompatible h1:mbxiZy1K820hQ+dI+YIO/+a0wQDYqOu18BAGe4lXjVk=
github.com/Azure/azure-sdk-for-go v59.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
github.com/Azure/go-autorest/autorest v0.10.1/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk=
github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM=
github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest v0.11.22 h1:bXiQwDjrRmBQOE67bwlvUKAC1EU1yZTPQ38c+bstZws=
github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk=
github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.9 h1:Y2CgdzitFDsdMwYMzf9LIZWrrTFysqbRc7b94XVVJ78=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU=
github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
@@ -75,7 +75,6 @@ github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRY
github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
@@ -255,15 +254,16 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/boundary/sdk v0.0.12-0.20211014161407-f01a8060a066 h1:uiFO3cBZ/y/5XA+gF6wp9OdrIkiZA9mS7ZHLpl3SDsM=
github.com/hashicorp/boundary/sdk v0.0.12-0.20211014161407-f01a8060a066/go.mod h1:jP6jIVYOS4WLPxsE3f8290nLMHBvUsbFQT1fEgzJmrY=
github.com/hashicorp/boundary/sdk v0.0.12-0.20211112235128-46a1edcb3b7b h1:qURjJaF3/TUbDcPCUAg9PG9a95NCgPrrcrOsurBJ7Rc=
github.com/hashicorp/boundary/sdk v0.0.12-0.20211112235128-46a1edcb3b7b/go.mod h1:Krt7KLGTnDPQTaVlgSygV5SeqWDrOfbYyVBmB0KILD4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/eventlogger v0.1.0 h1:S6xc4gZVzewuDUP4R4Ngko419h/CGDuV/b4ADL3XLik=
github.com/hashicorp/eventlogger v0.1.0/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.3 h1:RKYUGplnpDoE/Cj2vupQqUJS/wWBVvmpxEYeEpGL88s=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.3/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
github.com/hashicorp/eventlogger v0.1.1-0.20211106154408-4ff8da3a890c h1:u1Zq+LfTXaagmJCHK3XH/fi7pk7oRY933VjGNG7/Ynw=
github.com/hashicorp/eventlogger v0.1.1-0.20211106154408-4ff8da3a890c/go.mod h1:NaXU8p/pl5a2RX/N0/yncinT3Iw5CLkbF4JRxAVnk3c=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 h1:Yh9tY0lige+y0trmjQeT9NRDo6+YvtNAzbmUNOsIUzI=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
@@ -303,8 +303,9 @@ github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 h1:ai0P91rxlyGWkYUH/zFr7
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2/go.mod h1:EN1DJMjv9y5e/uRAuP2WPibAox5KHEiq2BjZl00aNPQ=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1 h1:HlTofNQN49DTzPCMVDppp+AVxMNVWaHUElpUYC0TRRE=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1/go.mod h1:irE9ILwhE8VooWdhHhBy0qXzOMRlHKoWoMlr31mAFCE=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 h1:Tz6v3Jb2DRnDCfifRSjYKG0m8dLdNq6bcDkB41en7nw=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788=
@@ -368,8 +369,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/manicminer/hamilton v0.32.0 h1:awVHVXOLp9P5QbWaOFqneuWjAvzAk3BYtTEX6AeWB5k=
github.com/manicminer/hamilton v0.32.0/go.mod h1:QryxpD/4+cdKuXNi0UjLDvgxYdP0LLmYz7dYU7DAX4U=
github.com/manicminer/hamilton v0.33.0 h1:G58jBVHg2qsJqb5cPJ3tKzWj3JTf8gW3r5+4RmnEa0E=
github.com/manicminer/hamilton v0.33.0/go.mod h1:QryxpD/4+cdKuXNi0UjLDvgxYdP0LLmYz7dYU7DAX4U=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -395,8 +396,9 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
@@ -519,8 +521,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+143 -171
View File
@@ -9,196 +9,164 @@ import (
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
hauth "github.com/manicminer/hamilton/auth"
"github.com/manicminer/hamilton/environments"
"github.com/manicminer/hamilton/msgraph"
"github.com/manicminer/hamilton/odata"
"google.golang.org/protobuf/types/known/structpb"
"github.com/mitchellh/mapstructure"
)
type WrappedHamiltonConfig struct {
hauth.Config
SecretId string
type AuthorizationInfo struct {
HamiltonConfig hauth.Config
AuthParams AuthParams
}
func authParams(hc *hostcatalogs.HostCatalog, secrets *structpb.Struct) (map[string]string, error) {
if hc == nil {
type CatalogAttributes struct {
SubscriptionId string `mapstructure:"subscription_id"`
ClientId string `mapstructure:"client_id"`
TenantId string `mapstructure:"tenant_id"`
DisableCredentialRotation bool `mapstructure:"disable_credential_rotation"`
}
type SecretData struct {
SecretValue string `mapstructure:"secret_value"`
SecretId string `mapstructure:"secret_id"`
CredsLastRotatedTime string `mapstructure:"creds_last_rotated_time"`
}
type AuthParams struct {
SubscriptionId string
ClientId string
ClientObjectId string
TenantId string
SecretValue string
SecretId string
CredsLastRotatedTime time.Time
}
func getAuthorizationInfo(hc *hostcatalogs.HostCatalog) (*AuthorizationInfo, error) {
switch {
case hc == nil:
return nil, errors.New("host catalog is nil")
}
if hc.GetAttributes() == nil {
case hc.GetAttributes() == nil:
return nil, errors.New("host catalog attributes is nil")
}
hcFields := hc.GetAttributes().GetFields()
if hcFields == nil {
return nil, errors.New("host catalog attributes fields is nil")
case hc.GetSecrets() == nil:
return nil, errors.New("host catalog secret data is nil")
}
if secrets == nil {
return nil, errors.New("secrets is nil")
hcFields := hc.GetAttributes().AsMap()
var attrs CatalogAttributes
if err := mapstructure.Decode(hcFields, &attrs); err != nil {
return nil, fmt.Errorf("error decoding host catalog attribute fields: %w", err)
}
pdFields := secrets.GetFields()
if pdFields == nil {
return nil, errors.New("secrets data fields is nil")
if attrs.SubscriptionId == "" {
return nil, errors.New("subscription id is empty")
}
if attrs.TenantId == "" {
return nil, errors.New("tenant id is empty")
}
var subscriptionId string
if hcFields[constSubscriptionId] == nil {
return nil, errors.New("subscription id not provided in incoming data")
pdFields := hc.GetSecrets().AsMap()
var secrets SecretData
if err := mapstructure.Decode(pdFields, &secrets); err != nil {
return nil, fmt.Errorf("error decoding host catalog persisted data fields: %w", err)
}
if subscriptionId = hcFields[constSubscriptionId].GetStringValue(); subscriptionId == "" {
return nil, errors.New("subscription id could not be read as a string value")
if secrets.SecretValue == "" {
return nil, errors.New("secret value is empty")
}
ret := map[string]string{
constSubscriptionId: subscriptionId,
}
clientId := hcFields[constClientId]
secretValue := pdFields[constSecretValue]
secretId := pdFields[constSecretId]
tenantId := hcFields[constTenantId]
switch {
case clientId != nil && clientId.GetStringValue() != "" &&
secretValue != nil && secretValue.GetStringValue() != "" &&
tenantId != nil && tenantId.GetStringValue() != "":
ret[constClientId] = clientId.GetStringValue()
ret[constSecretValue] = secretValue.GetStringValue()
ret[constTenantId] = tenantId.GetStringValue()
if secretId != nil {
ret[constSecretId] = secretId.GetStringValue()
}
}
return ret, nil
}
func fetchAuthorizer(hc *hostcatalogs.HostCatalog, secrets *structpb.Struct) (string, autorest.Authorizer, error) {
aParams, err := authParams(hc, secrets)
if err != nil {
return "", nil, fmt.Errorf("error finding authentication params: %w", err)
}
var authorizer autorest.Authorizer
// Switch between currently-understood authorization types
{
switch {
case aParams[constClientId] != "" &&
aParams[constSecretValue] != "" &&
aParams[constTenantId] != "":
credsConfig := auth.NewClientCredentialsConfig(
aParams[constClientId],
aParams[constSecretValue],
aParams[constTenantId],
)
authorizer, err = credsConfig.Authorizer()
if err != nil {
return "", nil, fmt.Errorf("error authorizing to Azure: %w", err)
}
default:
return "", nil, errors.New("no or incomplete authentication information available")
}
if authorizer == nil {
return "", nil, fmt.Errorf("authorizer after authorizing to Azure is nil")
}
}
return aParams[constSubscriptionId], authorizer, nil
}
func getWrappedHamiltonConfig(hc *hostcatalogs.HostCatalog, persisted *pb.HostCatalogPersisted) (*WrappedHamiltonConfig, error) {
secrets := hc.GetSecrets()
if secrets == nil && persisted != nil {
secrets = persisted.GetSecrets()
}
aParams, err := authParams(hc, secrets)
if err != nil {
return nil, fmt.Errorf("error fetching authentication params when fetching: %w", err)
}
authConfig := &WrappedHamiltonConfig{}
authConfig.Environment = environments.Global
switch {
case aParams[constClientId] != "" &&
aParams[constSecretValue] != "" &&
aParams[constTenantId] != "":
authConfig.TenantID = aParams[constTenantId]
authConfig.ClientID = aParams[constClientId]
authConfig.ClientSecret = aParams[constSecretValue]
if aParams[constSecretId] != "" {
authConfig.SecretId = aParams[constSecretId]
}
authConfig.EnableClientSecretAuth = true
default:
return nil, errors.New("no or incomplete authentication information available")
}
return authConfig, nil
}
func getObjectId(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) (string, error) {
opts, err := getOpts(opt...)
if err != nil {
return "", fmt.Errorf("error parsing ops: %w", err)
}
objId := opts.withObjectId
if objId == "" {
clientId := opts.withClientId
if clientId == "" && hConfig != nil {
clientId = hConfig.ClientID
}
if clientId == "" {
return "", fmt.Errorf("neither client id nor object id passed in, cannot continue")
}
// First, find the appId
aClient, err := getApplicationsClient(ctx, hConfig)
var lastRotatedTime time.Time
var err error
if secrets.CredsLastRotatedTime != "" {
lastRotatedTime, err = time.Parse(time.RFC3339Nano, secrets.CredsLastRotatedTime)
if err != nil {
return "", fmt.Errorf("error getting application client: %w", err)
return nil, fmt.Errorf("error parsing last rotated time: %w", err)
}
if aClient == nil {
return "", errors.New("applications client is nil when getting object id")
}
apps, _, err := aClient.List(ctx, odata.Query{
Filter: fmt.Sprintf("appId eq '%s'", clientId),
})
if err != nil {
return "", fmt.Errorf("error listing applications to find objId: %w", err)
}
if apps == nil {
return "", errors.New("nil apps returned when listing to find objId")
}
if len(*apps) != 1 {
return "", fmt.Errorf("unexpected number of apps found, expected 1, found %d", len(*apps))
}
objId = *(*apps)[0].DirectoryObject.ID
}
return objId, nil
return &AuthorizationInfo{
HamiltonConfig: hauth.Config{
Environment: environments.Global,
TenantID: attrs.TenantId,
ClientID: attrs.ClientId,
ClientSecret: secrets.SecretValue,
EnableClientSecretAuth: true,
},
AuthParams: AuthParams{
SubscriptionId: attrs.SubscriptionId,
ClientId: attrs.ClientId,
TenantId: attrs.TenantId,
SecretId: secrets.SecretId,
SecretValue: secrets.SecretValue,
CredsLastRotatedTime: lastRotatedTime,
},
}, nil
}
func (a *AuthorizationInfo) autorestAuthorizer() (autorest.Authorizer, error) {
credsConfig := auth.NewClientCredentialsConfig(
a.AuthParams.ClientId,
a.AuthParams.SecretValue,
a.AuthParams.TenantId,
)
authorizer, err := credsConfig.Authorizer()
if err != nil {
return nil, fmt.Errorf("error authorizing to Azure: %w", err)
}
if authorizer == nil {
return nil, errors.New("generated authorizer is nil")
}
return authorizer, nil
}
func (a *AuthorizationInfo) populateObjectId(ctx context.Context, opt ...Option) error {
if a.AuthParams.ClientObjectId != "" {
return nil
}
if a.AuthParams.ClientId == "" {
return errors.New("client id not known at object id lookup time")
}
// First, find the appId
aClient, err := getApplicationsClient(ctx, a)
if err != nil {
return fmt.Errorf("error getting application client: %w", err)
}
if aClient == nil {
return errors.New("applications client is nil when getting object id")
}
apps, _, err := aClient.List(ctx, odata.Query{
Filter: fmt.Sprintf("appId eq '%s'", a.AuthParams.ClientId),
})
if err != nil {
return fmt.Errorf("error listing applications to find objId: %w", err)
}
if apps == nil {
return errors.New("nil apps returned when listing to find objId")
}
if len(*apps) != 1 {
return fmt.Errorf("unexpected number of apps found, expected 1, found %d", len(*apps))
}
a.AuthParams.ClientObjectId = *(*apps)[0].DirectoryObject.ID
return nil
}
// rotateCredentials creates a new password, then uses a client with that new
// password to revoke the old.
//
// NOTE: the underlying auth config will be modified to use the new credentials!
func rotateCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) (*msgraph.PasswordCredential, error) {
if hConfig == nil {
return nil, errors.New("empty auth config id")
func rotateCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) (*msgraph.PasswordCredential, error) {
if authzInfo == nil {
return nil, errors.New("empty authz info")
}
if hConfig.SecretId == "" {
if authzInfo.AuthParams.SecretId == "" {
return nil, errors.New("missing original secret id")
}
// Get the object ID here so we don't have to look it up twice
objId, err := getObjectId(ctx, hConfig)
if err != nil {
// Ensure the object ID is set
if err := authzInfo.populateObjectId(ctx); err != nil {
return nil, fmt.Errorf("error fetching object id: %w", err)
}
newCred, err := addCredential(ctx, hConfig, WithObjectId(objId))
newCred, err := addCredential(ctx, authzInfo)
if err != nil {
return nil, fmt.Errorf("error adding password: %w", err)
}
@@ -209,19 +177,23 @@ func rotateCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt .
return nil, errors.New("new credential secret text is nil after adding")
}
if err := removeCredential(ctx, hConfig, WithObjectId(objId)); err != nil {
if err := removeCredential(ctx, authzInfo); err != nil {
return nil, fmt.Errorf("error removing previous credential: %w", err)
}
return newCred, nil
}
func addCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) (*msgraph.PasswordCredential, error) {
objId, err := getObjectId(ctx, hConfig, opt...)
if err != nil {
func addCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) (*msgraph.PasswordCredential, error) {
if authzInfo == nil {
return nil, errors.New("empty authz info")
}
// Ensure the object ID is set
if err := authzInfo.populateObjectId(ctx); err != nil {
return nil, fmt.Errorf("error fetching object id: %w", err)
}
aClient, err := getApplicationsClient(ctx, hConfig)
aClient, err := getApplicationsClient(ctx, authzInfo)
if err != nil {
return nil, fmt.Errorf("error getting application client: %w", err)
}
@@ -231,7 +203,7 @@ func addCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...O
// Create the new password
displayName := fmt.Sprintf("boundary-rotated-%s", time.Now().Format(time.RFC3339))
newPass, _, err := aClient.AddPassword(ctx, objId, msgraph.PasswordCredential{
newPass, _, err := aClient.AddPassword(ctx, authzInfo.AuthParams.ClientObjectId, msgraph.PasswordCredential{
DisplayName: &displayName,
})
if err != nil {
@@ -250,9 +222,9 @@ func addCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...O
return newPass, nil
}
func removeCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) error {
objId, err := getObjectId(ctx, hConfig, opt...)
if err != nil {
func removeCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) error {
// Ensure the object ID is set
if err := authzInfo.populateObjectId(ctx); err != nil {
return fmt.Errorf("error fetching object id: %w", err)
}
@@ -261,12 +233,12 @@ func removeCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt .
return fmt.Errorf("error parsing ops: %w", err)
}
secretId := hConfig.SecretId
secretId := authzInfo.AuthParams.SecretId
if opts.withSecretId != "" {
secretId = opts.withSecretId
}
aClient, err := getApplicationsClient(ctx, hConfig)
aClient, err := getApplicationsClient(ctx, authzInfo)
if err != nil {
return fmt.Errorf("error getting application client: %w", err)
}
@@ -274,7 +246,7 @@ func removeCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt .
return errors.New("applications client is nil when removing credential")
}
_, err = aClient.RemovePassword(ctx, objId, secretId)
_, err = aClient.RemovePassword(ctx, authzInfo.AuthParams.ClientObjectId, secretId)
if err != nil {
return fmt.Errorf("error removing old password: %w", err)
}
+4 -7
View File
@@ -112,20 +112,17 @@ func getPublicIpAddressesClient(opt ...Option) (*network.PublicIPAddressesClient
return &client, nil
}
func getApplicationsClient(ctx context.Context, authConfig *WrappedHamiltonConfig) (*msgraph.ApplicationsClient, error) {
if authConfig == nil {
func getApplicationsClient(ctx context.Context, authzInfo *AuthorizationInfo) (*msgraph.ApplicationsClient, error) {
if authzInfo == nil {
return nil, errors.New("empty auth config id when fetching service principals client")
}
if authConfig.TenantID == "" {
return nil, errors.New("empty tenant id in auth config when fetching service principals client")
}
authorizer, err := authConfig.NewAuthorizer(ctx, hauth.MsGraph)
authorizer, err := authzInfo.HamiltonConfig.NewAuthorizer(ctx, hauth.MsGraph)
if err != nil {
return nil, fmt.Errorf("error fetching hauth authorizer when fetching service principals client: %w", err)
}
client := msgraph.NewApplicationsClient(authConfig.TenantID)
client := msgraph.NewApplicationsClient(authzInfo.AuthParams.TenantId)
client.BaseClient.Authorizer = authorizer
return client, nil
+12 -21
View File
@@ -26,16 +26,15 @@ type Option func(*options) error
// options = how options are represented
type options struct {
withHostCatalogAttrs *structpb.Struct
withPersistedData *structpb.Struct
withSubscriptionId string
withAuthorizer autorest.Authorizer
withUserAgent string
withBaseUrl string
withWrappedHamiltonConfig *WrappedHamiltonConfig
withClientId string
withObjectId string
withSecretId string
withHostCatalogAttrs *structpb.Struct
withPersistedData *structpb.Struct
withSubscriptionId string
withAuthorizer autorest.Authorizer
withUserAgent string
withBaseUrl string
withAuthorizationInfo *AuthorizationInfo
withClientId string
withSecretId string
}
func getDefaultOptions() options {
@@ -100,10 +99,10 @@ func WithBaseUrl(with string) Option {
}
}
// WithWrappedHamiltonConfig contains the hamilton package authentication config to use
func WithWrappedHamiltonConfig(with *WrappedHamiltonConfig) Option {
// WithAuthorizationInfo contains the authorization information to use
func WithAuthorizationInfo(with *AuthorizationInfo) Option {
return func(o *options) error {
o.withWrappedHamiltonConfig = with
o.withAuthorizationInfo = with
return nil
}
}
@@ -116,14 +115,6 @@ func WithClientId(with string) Option {
}
}
// WithObjectId contains the object ID to use
func WithObjectId(with string) Option {
return func(o *options) error {
o.withObjectId = with
return nil
}
}
// WithSecretId contains the secret ID to use
func WithSecretId(with string) Option {
return func(o *options) error {
+178 -169
View File
@@ -9,10 +9,13 @@ import (
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources"
"github.com/kr/pretty"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
"github.com/mitchellh/mapstructure"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
)
@@ -26,12 +29,16 @@ var (
_ pb.HostPluginServiceServer = (*AzurePlugin)(nil)
)
type SetAttributes struct {
Filter string `mapstructure:"filter"`
}
func rotateCredFromCallback(ctx context.Context, catalog *hostcatalogs.HostCatalog) (*pb.HostCatalogPersisted, error) {
authConfig, err := getWrappedHamiltonConfig(catalog, nil)
authzInfo, err := getAuthorizationInfo(catalog)
if err != nil {
return nil, fmt.Errorf("error getting hauth config when creating catalog: %w", err)
}
newPass, err := rotateCredential(ctx, authConfig)
newPass, err := rotateCredential(ctx, authzInfo)
if err != nil {
return nil, fmt.Errorf("error rotating credentials when creating catalog: %w", err)
}
@@ -51,208 +58,111 @@ func rotateCredFromCallback(ctx context.Context, catalog *hostcatalogs.HostCatal
}, err
}
func (p *AzurePlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalogRequest) (*pb.OnCreateCatalogResponse, error) {
catalog := req.GetCatalog()
if catalog == nil {
return nil, errors.New("catalog is nil")
func (p *AzurePlugin) OnCreateCatalog(_ context.Context, req *pb.OnCreateCatalogRequest) (*pb.OnCreateCatalogResponse, error) {
if err := validateCatalog(req.GetCatalog()); err != nil {
return nil, err
}
secrets := catalog.GetSecrets()
if secrets == nil {
// Secrets will have to be provided later, on update
return nil, nil
if err := validateSecrets(req.GetCatalog().GetSecrets()); err != nil {
return nil, err
}
sanitizeSecretsFields(secrets)
// Check if rotation is skipped. Always prefer attempting rotation unless we
// explicitly are told not to
var skipRotation bool
if attrs := catalog.GetAttributes(); attrs != nil {
if fields := attrs.GetFields(); fields != nil {
if field := fields[constDisableCredentialRotation]; field != nil {
if field.GetBoolValue() {
skipRotation = true
}
}
}
secrets := req.GetCatalog().GetSecrets().AsMap()
persistedSecrets := map[string]interface{}{
constSecretValue: secrets[constSecretValue],
}
if skipRotation {
return &pb.OnCreateCatalogResponse{
Persisted: &pb.HostCatalogPersisted{
Secrets: secrets,
},
}, nil
if _, ok := secrets[constSecretId]; ok {
persistedSecrets[constSecretId] = secrets[constSecretId]
}
newPersisted, err := rotateCredFromCallback(ctx, catalog)
persist, err := structpb.NewStruct(persistedSecrets)
if err != nil {
return nil, fmt.Errorf("error formatting new persisted data: %w", err)
return nil, status.Errorf(codes.FailedPrecondition, "failed marshaling persisted secrets: %q", err.Error())
}
ret := &pb.OnCreateCatalogResponse{
Persisted: newPersisted,
}
return ret, nil
return &pb.OnCreateCatalogResponse{
Persisted: &pb.HostCatalogPersisted{
Secrets: persist,
},
}, nil
}
func (p *AzurePlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalogRequest) (*pb.OnUpdateCatalogResponse, error) {
newCatalog := req.GetNewCatalog()
if newCatalog == nil {
// Should never happen
return nil, errors.New("new catalog is nil")
func (p *AzurePlugin) OnUpdateCatalog(_ context.Context, req *pb.OnUpdateCatalogRequest) (*pb.OnUpdateCatalogResponse, error) {
if err := validateCatalog(req.GetNewCatalog()); err != nil {
return nil, err
}
currentCatalog := req.GetCurrentCatalog()
if currentCatalog == nil {
// Should never happen
return nil, errors.New("current catalog is nil")
return nil, status.Error(codes.FailedPrecondition, "current catalog is nil")
}
secrets := newCatalog.GetSecrets()
secrets := req.GetNewCatalog().GetSecrets()
if secrets == nil {
// If new secrets weren't passed in, don't rotate what we have on
// update.
//
// TODO: There may be combinations of attributes where those values
// changing (or being added) should trigger rotation.
return nil, nil
return &pb.OnUpdateCatalogResponse{}, nil
}
if err := validateSecrets(secrets); err != nil {
return nil, err
}
sanitizeSecretsFields(secrets)
// If the old credential had been rotated, attempt to delete the old
// credential using itself; if it fails, proceed anyways, since in theory
// only Boundary knows this value so simply forgetting it is safe enough (if
// not clean).
var field *structpb.Value
var pdFields map[string]*structpb.Value
var err error
var authConfig *WrappedHamiltonConfig
persisted := req.GetPersisted()
if persisted == nil {
goto after_revocation
secretsMap := secrets.AsMap()
persistedSecrets := map[string]interface{}{
constSecretValue: secretsMap[constSecretValue],
}
if persisted.GetSecrets() == nil {
goto after_revocation
if _, ok := secretsMap[constSecretId]; ok {
persistedSecrets[constSecretId] = secretsMap[constSecretId]
}
pdFields = persisted.GetSecrets().GetFields()
if pdFields == nil {
goto after_revocation
}
field = pdFields[constCredsLastRotatedTime]
if field == nil {
goto after_revocation
}
// If the field exists, it is a timestamp here indicating last rotation time
authConfig, err = getWrappedHamiltonConfig(currentCatalog, req.GetPersisted())
persist, err := structpb.NewStruct(persistedSecrets)
if err != nil {
// TODO: warn in some way?
goto after_revocation
}
if err := removeCredential(ctx, authConfig); err != nil {
// TODO: warn
goto after_revocation
return nil, status.Errorf(codes.FailedPrecondition, "failed marshaling persisted secrets: %q", err.Error())
}
after_revocation:
// Check if rotation is skipped. Always prefer attempting rotation unless we
// explicitly are told not to
var skipRotation bool
if attrs := newCatalog.GetAttributes(); attrs != nil {
if fields := attrs.GetFields(); fields != nil {
if field := fields[constDisableCredentialRotation]; field != nil {
if field.GetBoolValue() {
skipRotation = true
}
}
}
}
if skipRotation {
return &pb.OnUpdateCatalogResponse{
Persisted: &pb.HostCatalogPersisted{
Secrets: secrets,
},
}, nil
}
newPersisted, err := rotateCredFromCallback(ctx, req.GetNewCatalog())
if err != nil {
return nil, fmt.Errorf("error formatting new persisted data: %w", err)
}
ret := &pb.OnUpdateCatalogResponse{
Persisted: newPersisted,
}
return ret, nil
return &pb.OnUpdateCatalogResponse{
Persisted: &pb.HostCatalogPersisted{
Secrets: persist,
},
}, nil
}
func (p *AzurePlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalogRequest) (*pb.OnDeleteCatalogResponse, error) {
if req.GetCatalog() == nil {
return nil, errors.New("catalog is nil")
func (p *AzurePlugin) OnCreateSet(_ context.Context, req *pb.OnCreateSetRequest) (*pb.OnCreateSetResponse, error) {
if err := validateSet(req.GetSet()); err != nil {
return nil, err
}
if req.GetPersisted() == nil || req.GetPersisted().GetSecrets() == nil {
return nil, nil
}
// Figure out if creds were rotated
pdFields := req.GetPersisted().GetSecrets().GetFields()
if pdFields == nil {
// No data
return nil, nil
}
field := pdFields[constCredsLastRotatedTime]
if field == nil {
return nil, nil
}
// The field existing means it is a string containing the last time it was
// rotated. Delete the credential using itself.
authConfig, err := getWrappedHamiltonConfig(req.GetCatalog(), req.GetPersisted())
if err != nil {
return nil, fmt.Errorf("error getting auth config on catalog deletion: %w", err)
}
if err := removeCredential(ctx, authConfig); err != nil {
if err != nil {
return nil, fmt.Errorf("error revoking credential on catalog deletion: %w, authConfig is %s", err, pretty.Sprint(authConfig))
}
}
return nil, nil
return &pb.OnCreateSetResponse{}, nil
}
func (p *AzurePlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest) (*pb.OnCreateSetResponse, error) {
// Nothing yet
return nil, nil
}
func (p *AzurePlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest) (*pb.OnUpdateSetResponse, error) {
// Nothing yet
return nil, nil
}
func (p *AzurePlugin) OnDeleteSet(ctx context.Context, req *pb.OnDeleteSetRequest) (*pb.OnDeleteSetResponse, error) {
// Nothing yet
return nil, nil
func (p *AzurePlugin) OnUpdateSet(_ context.Context, req *pb.OnUpdateSetRequest) (*pb.OnUpdateSetResponse, error) {
if err := validateSet(req.GetNewSet()); err != nil {
return nil, err
}
return &pb.OnUpdateSetResponse{}, nil
}
func (p *AzurePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*pb.ListHostsResponse, error) {
if len(req.GetSets()) == 0 {
// Nothing to fetch
return nil, nil
return &pb.ListHostsResponse{}, nil
}
catalog := req.GetCatalog()
if catalog == nil {
return nil, errors.New("catalog is nil")
return nil, status.Error(codes.FailedPrecondition, "catalog is nil")
}
catalogAttrs := catalog.GetAttributes()
if catalogAttrs == nil {
return nil, errors.New("catalog has no attributes")
return nil, status.Error(codes.FailedPrecondition, "catalog has no attributes")
}
// Create an authorizer
var secrets *structpb.Struct
if req.GetPersisted() != nil {
secrets = req.GetPersisted().GetSecrets()
}
subscriptionId, authorizer, err := fetchAuthorizer(catalog, secrets)
catalog.Secrets = req.GetPersisted().GetSecrets()
authzInfo, err := getAuthorizationInfo(catalog)
if err != nil {
return nil, fmt.Errorf("error fetching subscription id and authorizer: %w", err)
return nil, fmt.Errorf("error fetching authorizationInfo: %w", err)
}
if subscriptionId == "" {
return nil, errors.New("subscription id not discovered")
if authzInfo == nil {
return nil, status.Error(codes.FailedPrecondition, "authorization info is nil")
}
authorizer, err := authzInfo.autorestAuthorizer()
if err != nil {
return nil, fmt.Errorf("error fetching autorest authorizer: %w", err)
}
if authorizer == nil {
return nil, errors.New("fetched authorizer is nil")
@@ -260,7 +170,7 @@ func (p *AzurePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (
commonOpts := make([]Option, 0, 3)
commonOpts = append(commonOpts,
WithSubscriptionId(subscriptionId),
WithSubscriptionId(authzInfo.AuthParams.SubscriptionId),
WithAuthorizer(authorizer),
)
@@ -384,11 +294,11 @@ func (p *AzurePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (
return nil, fmt.Errorf("instance view statuses returned for vm with id %q is null", *res.ID)
}
var running bool
for _, status := range *iv.Statuses {
if status.Code == nil {
for _, s := range *iv.Statuses {
if s.Code == nil {
continue
}
state := strings.ToLower(*status.Code)
state := strings.ToLower(*s.Code)
prefix := "powerstate/"
if !strings.HasPrefix(state, prefix) {
continue
@@ -504,9 +414,108 @@ func splitId(in, expectedService, expectedResource string) (string, string, erro
return splitId[3], splitId[7], nil
}
func sanitizeSecretsFields(in *structpb.Struct) {
// Only we are allowed to set this value
if fields := in.GetFields(); fields != nil {
delete(fields, constCredsLastRotatedTime)
// Returns an invalid argument error with the problematic fields included
// in the error message.
func invalidArgumentError(msg string, f map[string]string) error {
var fieldMsgs []string
for field, val := range f {
fieldMsgs = append(fieldMsgs, fmt.Sprintf("%q: %q", field, val))
}
if len(fieldMsgs) > 0 {
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
}
return status.Error(codes.InvalidArgument, msg)
}
func validateSecrets(s *structpb.Struct) error {
if s == nil {
return status.Error(codes.InvalidArgument, "Secrets not provided but are required")
}
var secrets SecretData
if err := mapstructure.Decode(s.AsMap(), &secrets); err != nil {
return status.Errorf(codes.InvalidArgument, "error decoding catalog secrets: %s", err)
}
badFields := make(map[string]string)
if len(secrets.SecretValue) == 0 {
badFields["secrets.secret_value"] = "This field is required."
}
if len(secrets.CredsLastRotatedTime) != 0 {
badFields["secrets.creds_last_rotated_time"] = "This field is reserved and cannot be set."
}
if len(badFields) > 0 {
return invalidArgumentError("Error in the secrets provided", badFields)
}
return nil
}
var allowedCatalogFields = map[string]struct{}{
constDisableCredentialRotation: {},
constSubscriptionId: {},
constTenantId: {},
constClientId: {},
}
func validateCatalog(c *hostcatalogs.HostCatalog) error {
if c == nil {
return status.Error(codes.InvalidArgument, "catalog is nil")
}
var attrs CatalogAttributes
attrMap := c.GetAttributes().AsMap()
if err := mapstructure.Decode(attrMap, &attrs); err != nil {
return status.Errorf(codes.InvalidArgument, "error decoding catalog attributes: %s", err)
}
badFields := make(map[string]string)
if !attrs.DisableCredentialRotation {
badFields["attributes.disable_credential_rotation"] = "This field must be set to true."
}
if len(attrs.SubscriptionId) == 0 {
badFields["attributes.subscription_id"] = "This is a required field."
}
if len(attrs.ClientId) == 0 {
badFields["attributes.client_id"] = "This is a required field."
}
if len(attrs.TenantId) == 0 {
badFields["attributes.tenant_id"] = "This is a required field."
}
for f := range attrMap {
if _, ok := allowedCatalogFields[f]; !ok {
badFields[fmt.Sprintf("attributes.%s", f)] = "Unrecognized field."
}
}
if len(badFields) > 0 {
return invalidArgumentError("Invalid arguments in the new catalog", badFields)
}
return nil
}
var allowedSetFields = map[string]struct{}{
constFilter: {},
}
func validateSet(s *hostsets.HostSet) error {
if s == nil {
return status.Error(codes.InvalidArgument, "set is nil")
}
var attrs SetAttributes
attrMap := s.GetAttributes().AsMap()
if err := mapstructure.Decode(attrMap, &attrs); err != nil {
return status.Errorf(codes.InvalidArgument, "error decoding set attributes: %s", err)
}
badFields := make(map[string]string)
if _, ok := attrMap[constFilter]; ok && len(attrs.Filter) == 0 {
badFields["attributes.filter"] = "This field must be not empty."
}
for f := range attrMap {
if _, ok := allowedSetFields[f]; !ok {
badFields[fmt.Sprintf("attributes.%s", f)] = "Unrecognized field."
}
}
if len(badFields) > 0 {
return invalidArgumentError("Invalid arguments in the new set", badFields)
}
return nil
}
+320 -126
View File
@@ -15,6 +15,7 @@ import (
"github.com/manicminer/hamilton/auth"
"github.com/manicminer/hamilton/environments"
"github.com/manicminer/hamilton/msgraph"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)
@@ -46,116 +47,315 @@ func TestListHosts(t *testing.T) {
pretty.Println(lhResp.GetHosts())
}
func TestOnCreateOnDeleteCatalog(t *testing.T) {
require := require.New(t)
ctx := context.Background()
p := &AzurePlugin{}
hostCatalog, _ := testGetHostStructs(t)
objId, initialAuthConfig, initialCred, cleanup := testCreateInitialSecret(t, hostCatalog)
defer cleanup()
// At this point, remove secrets corresponding to those read in by
// testGetHostCatalog as they're our permanent ones
hostCatalog.Secrets = nil
// Give some time -- a lot -- for token to become active
time.Sleep(10 * time.Minute)
// Now, we'll use this credential to simulate creating a host catalog, which
// will rotate it. First, ensure we don't error out if creds aren't provided
// _yet_ (instead, on update)
ccResp, err := p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{
Catalog: hostCatalog,
})
require.NoError(err)
require.Nil(ccResp)
// Now run the same thing with rotation disabled, make sure we get the same
// values back
secrets := wrapMap(t, map[string]interface{}{
constSecretValue: *initialCred.SecretText,
constSecretId: *initialCred.KeyId,
})
hostCatalog.Secrets = secrets
hostCatalog.GetAttributes().GetFields()[constDisableCredentialRotation] = structpb.NewBoolValue(true)
ccResp, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{
Catalog: hostCatalog,
})
require.NoError(err)
require.NotNil(ccResp)
require.NotNil(ccResp.GetPersisted())
require.NotNil(ccResp.GetPersisted().GetSecrets())
newPdFields := ccResp.GetPersisted().GetSecrets().GetFields()
require.Equal(*initialCred.KeyId, newPdFields[constSecretId].GetStringValue())
require.Equal(*initialCred.SecretText, newPdFields[constSecretValue].GetStringValue())
// Now we'll try again, actually rotating the creds
delete(hostCatalog.GetAttributes().GetFields(), constDisableCredentialRotation)
ccResp, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{
Catalog: hostCatalog,
})
require.NoError(err)
require.NotNil(ccResp)
require.NotNil(ccResp.GetPersisted())
require.NotNil(ccResp.GetPersisted().GetSecrets())
newPdFields = ccResp.GetPersisted().GetSecrets().GetFields()
// Use old creds for this removal to give it a high likelihood of success
deferAuthConfig := *initialAuthConfig
defer func() {
_ = removeCredential(
ctx,
&deferAuthConfig,
WithObjectId(objId),
)
}()
// Best-effort ensure that this new cred is deleted if OnDelete doesn't work right
require.NotEqual(*initialCred.KeyId, newPdFields[constSecretId].GetStringValue())
require.NotEqual(*initialCred.SecretText, newPdFields[constSecretValue].GetStringValue())
// Remove these now prior to OnDelete
hostCatalog.Secrets = nil
// Make sure old creds don't work
waitForCredsAuthConfig := *initialAuthConfig
// FIXME: Actually don't, because they can still be used for up to a day!??! later?!?!
// waitForCreds(t, &waitForCredsAuthConfig, false)
// Make sure the new creds work, then sleep
waitForCredsAuthConfig.SecretId = newPdFields[constSecretId].GetStringValue()
waitForCredsAuthConfig.ClientSecret = newPdFields[constSecretValue].GetStringValue()
waitForCreds(t, &waitForCredsAuthConfig, true)
time.Sleep(10 * time.Minute)
// Now, let's test OnDelete. First, check the case where we never configured
// a credential in the first place.
cdResp, err := p.OnDeleteCatalog(ctx, &pb.OnDeleteCatalogRequest{
Catalog: hostCatalog,
Persisted: &pb.HostCatalogPersisted{},
})
require.NoError(err)
require.Nil(cdResp)
// Now, use the credentials we got above to revoke this credential we got from OnCreate
secrets = wrapMap(t, map[string]interface{}{
constSecretValue: waitForCredsAuthConfig.ClientSecret,
constSecretId: waitForCredsAuthConfig.SecretId,
})
cdResp, err = p.OnDeleteCatalog(ctx, &pb.OnDeleteCatalogRequest{
Catalog: hostCatalog,
Persisted: &pb.HostCatalogPersisted{
Secrets: secrets,
func TestValidateCatalog(t *testing.T) {
cases := []struct {
name string
catalogAttrs *structpb.Struct
wantError bool
}{
{
name: "success",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
},
})
require.NoError(err)
require.Nil(cdResp)
// Make sure the creds no longer work
// FIXME: Don't, for the same reason
// waitForCreds(t, &waitForCredsAuthConfig, false)
{
name: "bad type",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: "bad type",
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "nil",
catalogAttrs: nil,
wantError: true,
},
{
name: "rotation enabled",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: false,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "no client id",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "no tenant id",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: false,
constClientId: "client_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "no subscription id",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: false,
constClientId: "client_id",
constTenantId: "tenant_id",
}),
wantError: true,
},
{
name: "unrecognized field",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
"unrecognized": "unrecognized",
}),
wantError: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := validateCatalog(&hostcatalogs.HostCatalog{
Attributes: tc.catalogAttrs,
})
if tc.wantError {
assert.Error(t, got)
} else {
assert.NoError(t, got)
}
})
}
}
func waitForCreds(t *testing.T, hConfig *WrappedHamiltonConfig, shouldWork bool) auth.Authorizer {
func TestValidateSecrets(t *testing.T) {
cases := []struct {
name string
catalogSecrets *structpb.Struct
wantError bool
}{
{
name: "success",
catalogSecrets: wrapMap(t, map[string]interface{}{
constSecretValue: "value",
constSecretId: "secret_id",
}),
},
{
name: "nil secret value",
catalogSecrets: nil,
wantError: true,
},
{
name: "no secret value",
catalogSecrets: wrapMap(t, map[string]interface{}{
constSecretId: "secret_id",
}),
wantError: true,
},
{
name: "secret with last rotated time",
catalogSecrets: wrapMap(t, map[string]interface{}{
constSecretValue: "value",
constCredsLastRotatedTime: "something",
}),
wantError: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := validateSecrets(tc.catalogSecrets)
if tc.wantError {
assert.Error(t, got)
} else {
assert.NoError(t, got)
}
})
}
}
func TestValidateSet(t *testing.T) {
cases := []struct {
name string
setAttrs *structpb.Struct
wantError bool
}{
{
name: "success",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: "filter value",
}),
},
{
name: "nil",
setAttrs: nil,
},
{
name: "bad type",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: true,
}),
wantError: true,
},
{
name: "empty",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: "",
}),
wantError: true,
},
{
name: "unrecognized field",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: "filter value",
"unrecognized": "unrecognized",
}),
wantError: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := validateSet(&hostsets.HostSet{
Attributes: tc.setAttrs,
})
if tc.wantError {
assert.Error(t, got)
} else {
assert.NoError(t, got)
}
})
}
}
func TestCreateCatalog(t *testing.T) {
ctx := context.Background()
p := &AzurePlugin{}
attrs := wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
})
secrets := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
})
res, err := p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
Secrets: secrets,
}})
require.NoError(t, err)
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithExtra := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
"extraField": "extra_value",
})
res, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
Secrets: secretsWithExtra,
}})
require.NoError(t, err)
// still only persist the fields we care about.
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithRotationTime := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
constCredsLastRotatedTime: "something",
})
res, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
Secrets: secretsWithRotationTime,
}})
assert.Error(t, err)
}
func TestUpdateCatalog(t *testing.T) {
ctx := context.Background()
p := &AzurePlugin{}
oldCatalog := &hostcatalogs.HostCatalog{
Attributes: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "foo",
constTenantId: "foo",
constSubscriptionId: "foo",
}),
}
attrs := wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
})
res, err := p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
}})
require.NoError(t, err)
assert.Nil(t, res.GetPersisted())
secrets := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
})
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
Secrets: secrets,
}})
require.NoError(t, err)
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithExtra := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
"extraField": "extra_value",
})
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
Secrets: secretsWithExtra,
}})
require.NoError(t, err)
// still only persist the fields we care about.
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithRotationTime := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
constCredsLastRotatedTime: "something",
})
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
Secrets: secretsWithRotationTime,
}})
assert.Error(t, err)
}
func waitForCreds(t *testing.T, authzInfo *AuthorizationInfo, shouldWork bool) auth.Authorizer {
t.Helper()
require := require.New(t)
ctx := context.Background()
@@ -164,9 +364,9 @@ func waitForCreds(t *testing.T, hConfig *WrappedHamiltonConfig, shouldWork bool)
environments.Global,
auth.MsGraph,
auth.TokenVersion2,
hConfig.TenantID,
hConfig.ClientID,
hConfig.ClientSecret)
authzInfo.AuthParams.TenantId,
authzInfo.AuthParams.ClientId,
authzInfo.AuthParams.SecretValue)
require.NoError(err)
for {
@@ -230,45 +430,39 @@ func testGetHostStructs(t *testing.T) (*hostcatalogs.HostCatalog, []*hostsets.Ho
// leading into a test. This will serve as our "initial secret" for a test. We
// also return a few cached items. It is expected that the host catalog contains
// Secrets.
func testCreateInitialSecret(t *testing.T, hostCatalog *hostcatalogs.HostCatalog) (string, *WrappedHamiltonConfig, *msgraph.PasswordCredential, func()) {
func testCreateInitialSecret(t *testing.T, hostCatalog *hostcatalogs.HostCatalog) (*AuthorizationInfo, *msgraph.PasswordCredential, func()) {
require := require.New(t)
ctx := context.Background()
var initialCred *msgraph.PasswordCredential
var objId string // The object ID of the application
// Set up the data, get the config
persistedData := hostCatalog.GetSecrets()
authConfig, err := getWrappedHamiltonConfig(hostCatalog, &pb.HostCatalogPersisted{Secrets: persistedData})
require.NoError(err)
// Pre-cache the object ID
objId, err = getObjectId(ctx, authConfig, WithClientId(hostCatalog.GetAttributes().GetFields()[constClientId].GetStringValue()))
authzInfo, err := getAuthorizationInfo(hostCatalog)
require.NoError(err)
// Get the credential
initialCred, err = addCredential(ctx, authConfig, WithObjectId(objId))
initialCred, err = addCredential(ctx, authzInfo)
require.NoError(err)
require.NotNil(initialCred)
require.NotNil(initialCred.KeyId)
require.NotNil(initialCred.SecretText)
origAuthConfig := *authConfig
origAuthConfig := *authzInfo
origAuthConfig.AuthParams.SecretId = *initialCred.KeyId
cleanup := func() {
if err := removeCredential(
ctx,
&origAuthConfig,
WithObjectId(objId),
WithSecretId(*initialCred.KeyId),
); err != nil {
// It may not exist because it's been rotated
require.Contains(err.Error(), "No password credential found with keyId", err.Error())
}
}
authConfig.SecretId = *initialCred.KeyId
authConfig.ClientSecret = *initialCred.SecretText
waitForCreds(t, authConfig, true)
authzInfo.AuthParams.SecretId = *initialCred.KeyId
authzInfo.AuthParams.SecretValue = *initialCred.SecretText
authzInfo.HamiltonConfig.ClientSecret = *initialCred.SecretText
waitForCreds(t, authzInfo, true)
return objId, authConfig, initialCred, cleanup
return authzInfo, initialCred, cleanup
}