Compare commits

..
Author SHA1 Message Date
Todd Knight 32db0dcbea Update boundary/sdk dependency due to breaking proto change. 2021-10-29 13:43:25 -07:00
8 changed files with 529 additions and 618 deletions

No files matched your search

+14 -12
View File
@@ -19,22 +19,24 @@ 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
*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.
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.
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.
*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.
## Attributes and Secrets
+9 -55
View File
@@ -1,63 +1,17 @@
module github.com/hashicorp/boundary-plugin-host-azure
go 1.17
go 1.16
require (
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/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.20211028194642-6eba8a478703
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1
github.com/kr/pretty v0.3.0
github.com/manicminer/hamilton v0.33.0
github.com/mitchellh/mapstructure v1.4.2
github.com/manicminer/hamilton v0.32.0
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
)
+27 -22
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 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/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/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.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 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/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.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
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.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/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/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,6 +75,7 @@ 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=
@@ -165,6 +166,7 @@ github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -251,19 +253,19 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
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.20211112235128-46a1edcb3b7b h1:qURjJaF3/TUbDcPCUAg9PG9a95NCgPrrcrOsurBJ7Rc=
github.com/hashicorp/boundary/sdk v0.0.12-0.20211112235128-46a1edcb3b7b/go.mod h1:Krt7KLGTnDPQTaVlgSygV5SeqWDrOfbYyVBmB0KILD4=
github.com/hashicorp/boundary/sdk v0.0.12-0.20211028194642-6eba8a478703 h1:i7VpfFTZSWN0TQ5SHlrhe4stSPrfq+4bIu915oB7O54=
github.com/hashicorp/boundary/sdk v0.0.12-0.20211028194642-6eba8a478703/go.mod h1:rgZ7YvzX8AM1Vd681nAb+MpSApWNmUUnbyycpVWlylk=
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 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/eventlogger/filters/encrypt v0.1.5 h1:kNkH4G6zzWlZSoI1I+B/ud4chVKTPL516C6jB7dRdlE=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5/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,9 +305,8 @@ 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=
@@ -353,6 +354,7 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -369,8 +371,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.33.0 h1:G58jBVHg2qsJqb5cPJ3tKzWj3JTf8gW3r5+4RmnEa0E=
github.com/manicminer/hamilton v0.33.0/go.mod h1:QryxpD/4+cdKuXNi0UjLDvgxYdP0LLmYz7dYU7DAX4U=
github.com/manicminer/hamilton v0.32.0 h1:awVHVXOLp9P5QbWaOFqneuWjAvzAk3BYtTEX6AeWB5k=
github.com/manicminer/hamilton v0.32.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=
@@ -396,9 +398,8 @@ 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=
@@ -469,8 +470,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -479,6 +482,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -521,9 +525,8 @@ 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=
@@ -556,6 +559,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -845,6 +849,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+161 -133
View File
@@ -9,164 +9,196 @@ 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"
"github.com/mitchellh/mapstructure"
"google.golang.org/protobuf/types/known/structpb"
)
type AuthorizationInfo struct {
HamiltonConfig hauth.Config
AuthParams AuthParams
type WrappedHamiltonConfig struct {
hauth.Config
SecretId string
}
type Attributes 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:
func authParams(hc *hostcatalogs.HostCatalog, secrets *structpb.Struct) (map[string]string, error) {
if hc == nil {
return nil, errors.New("host catalog is nil")
case hc.GetAttributes() == nil:
}
if hc.GetAttributes() == nil {
return nil, errors.New("host catalog attributes is nil")
case hc.GetSecrets() == nil:
return nil, errors.New("host catalog secret data is nil")
}
hcFields := hc.GetAttributes().GetFields()
if hcFields == nil {
return nil, errors.New("host catalog attributes fields is nil")
}
hcFields := hc.GetAttributes().AsMap()
var attrs Attributes
if err := mapstructure.Decode(hcFields, &attrs); err != nil {
return nil, fmt.Errorf("error decoding host catalog attribute fields: %w", err)
if secrets == nil {
return nil, errors.New("secrets is nil")
}
if attrs.SubscriptionId == "" {
return nil, errors.New("subscription id is empty")
}
if attrs.TenantId == "" {
return nil, errors.New("tenant id is empty")
pdFields := secrets.GetFields()
if pdFields == nil {
return nil, errors.New("secrets data fields is nil")
}
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)
var subscriptionId string
if hcFields[constSubscriptionId] == nil {
return nil, errors.New("subscription id not provided in incoming data")
}
if secrets.SecretValue == "" {
return nil, errors.New("secret value is empty")
if subscriptionId = hcFields[constSubscriptionId].GetStringValue(); subscriptionId == "" {
return nil, errors.New("subscription id could not be read as a string value")
}
var lastRotatedTime time.Time
var err error
if secrets.CredsLastRotatedTime != "" {
lastRotatedTime, err = time.Parse(time.RFC3339Nano, secrets.CredsLastRotatedTime)
if err != nil {
return nil, fmt.Errorf("error parsing last rotated time: %w", err)
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 &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
return ret, nil
}
func (a *AuthorizationInfo) autorestAuthorizer() (autorest.Authorizer, error) {
credsConfig := auth.NewClientCredentialsConfig(
a.AuthParams.ClientId,
a.AuthParams.SecretValue,
a.AuthParams.TenantId,
)
authorizer, err := credsConfig.Authorizer()
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 authorizing to Azure: %w", err)
return "", nil, fmt.Errorf("error finding authentication params: %w", err)
}
if authorizer == nil {
return nil, errors.New("generated authorizer is nil")
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 authorizer, nil
return aParams[constSubscriptionId], authorizer, nil
}
func (a *AuthorizationInfo) populateObjectId(ctx context.Context, opt ...Option) error {
if a.AuthParams.ClientObjectId != "" {
return 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")
}
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 authConfig, nil
}
return 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)
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'", 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
}
// rotateCredentials creates a new password, then uses a client with that new
// password to revoke the old.
func rotateCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) (*msgraph.PasswordCredential, error) {
if authzInfo == nil {
return nil, errors.New("empty authz info")
//
// 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")
}
if authzInfo.AuthParams.SecretId == "" {
if hConfig.SecretId == "" {
return nil, errors.New("missing original secret id")
}
// Ensure the object ID is set
if err := authzInfo.populateObjectId(ctx); err != nil {
// Get the object ID here so we don't have to look it up twice
objId, err := getObjectId(ctx, hConfig)
if err != nil {
return nil, fmt.Errorf("error fetching object id: %w", err)
}
newCred, err := addCredential(ctx, authzInfo)
newCred, err := addCredential(ctx, hConfig, WithObjectId(objId))
if err != nil {
return nil, fmt.Errorf("error adding password: %w", err)
}
@@ -177,23 +209,19 @@ func rotateCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...
return nil, errors.New("new credential secret text is nil after adding")
}
if err := removeCredential(ctx, authzInfo); err != nil {
if err := removeCredential(ctx, hConfig, WithObjectId(objId)); err != nil {
return nil, fmt.Errorf("error removing previous credential: %w", err)
}
return newCred, 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 {
func addCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) (*msgraph.PasswordCredential, error) {
objId, err := getObjectId(ctx, hConfig, opt...)
if err != nil {
return nil, fmt.Errorf("error fetching object id: %w", err)
}
aClient, err := getApplicationsClient(ctx, authzInfo)
aClient, err := getApplicationsClient(ctx, hConfig)
if err != nil {
return nil, fmt.Errorf("error getting application client: %w", err)
}
@@ -203,7 +231,7 @@ func addCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Opt
// Create the new password
displayName := fmt.Sprintf("boundary-rotated-%s", time.Now().Format(time.RFC3339))
newPass, _, err := aClient.AddPassword(ctx, authzInfo.AuthParams.ClientObjectId, msgraph.PasswordCredential{
newPass, _, err := aClient.AddPassword(ctx, objId, msgraph.PasswordCredential{
DisplayName: &displayName,
})
if err != nil {
@@ -222,9 +250,9 @@ func addCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Opt
return newPass, nil
}
func removeCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) error {
// Ensure the object ID is set
if err := authzInfo.populateObjectId(ctx); err != nil {
func removeCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) error {
objId, err := getObjectId(ctx, hConfig, opt...)
if err != nil {
return fmt.Errorf("error fetching object id: %w", err)
}
@@ -233,12 +261,12 @@ func removeCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...
return fmt.Errorf("error parsing ops: %w", err)
}
secretId := authzInfo.AuthParams.SecretId
secretId := hConfig.SecretId
if opts.withSecretId != "" {
secretId = opts.withSecretId
}
aClient, err := getApplicationsClient(ctx, authzInfo)
aClient, err := getApplicationsClient(ctx, hConfig)
if err != nil {
return fmt.Errorf("error getting application client: %w", err)
}
@@ -246,7 +274,7 @@ func removeCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...
return errors.New("applications client is nil when removing credential")
}
_, err = aClient.RemovePassword(ctx, authzInfo.AuthParams.ClientObjectId, secretId)
_, err = aClient.RemovePassword(ctx, objId, secretId)
if err != nil {
return fmt.Errorf("error removing old password: %w", err)
}
+7 -4
View File
@@ -112,17 +112,20 @@ func getPublicIpAddressesClient(opt ...Option) (*network.PublicIPAddressesClient
return &client, nil
}
func getApplicationsClient(ctx context.Context, authzInfo *AuthorizationInfo) (*msgraph.ApplicationsClient, error) {
if authzInfo == nil {
func getApplicationsClient(ctx context.Context, authConfig *WrappedHamiltonConfig) (*msgraph.ApplicationsClient, error) {
if authConfig == 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 := authzInfo.HamiltonConfig.NewAuthorizer(ctx, hauth.MsGraph)
authorizer, err := authConfig.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(authzInfo.AuthParams.TenantId)
client := msgraph.NewApplicationsClient(authConfig.TenantID)
client.BaseClient.Authorizer = authorizer
return client, nil
+21 -12
View File
@@ -26,15 +26,16 @@ 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
withAuthorizationInfo *AuthorizationInfo
withClientId string
withSecretId string
withHostCatalogAttrs *structpb.Struct
withPersistedData *structpb.Struct
withSubscriptionId string
withAuthorizer autorest.Authorizer
withUserAgent string
withBaseUrl string
withWrappedHamiltonConfig *WrappedHamiltonConfig
withClientId string
withObjectId string
withSecretId string
}
func getDefaultOptions() options {
@@ -99,10 +100,10 @@ func WithBaseUrl(with string) Option {
}
}
// WithAuthorizationInfo contains the authorization information to use
func WithAuthorizationInfo(with *AuthorizationInfo) Option {
// WithWrappedHamiltonConfig contains the hamilton package authentication config to use
func WithWrappedHamiltonConfig(with *WrappedHamiltonConfig) Option {
return func(o *options) error {
o.withAuthorizationInfo = with
o.withWrappedHamiltonConfig = with
return nil
}
}
@@ -115,6 +116,14 @@ 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 {
+172 -132
View File
@@ -9,12 +9,10 @@ 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/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
"github.com/mitchellh/mapstructure"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/kr/pretty"
"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"
)
@@ -29,11 +27,11 @@ var (
)
func rotateCredFromCallback(ctx context.Context, catalog *hostcatalogs.HostCatalog) (*pb.HostCatalogPersisted, error) {
authzInfo, err := getAuthorizationInfo(catalog)
authConfig, err := getWrappedHamiltonConfig(catalog, nil)
if err != nil {
return nil, fmt.Errorf("error getting hauth config when creating catalog: %w", err)
}
newPass, err := rotateCredential(ctx, authzInfo)
newPass, err := rotateCredential(ctx, authConfig)
if err != nil {
return nil, fmt.Errorf("error rotating credentials when creating catalog: %w", err)
}
@@ -54,96 +52,207 @@ func rotateCredFromCallback(ctx context.Context, catalog *hostcatalogs.HostCatal
}
func (p *AzurePlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalogRequest) (*pb.OnCreateCatalogResponse, error) {
if err := validateCatalog(req.GetCatalog()); err != nil {
return nil, err
catalog := req.GetCatalog()
if catalog == nil {
return nil, errors.New("catalog is nil")
}
if err := validateSecrets(req.GetCatalog().GetSecrets()); err != nil {
return nil, err
secrets := catalog.GetSecrets()
if secrets == nil {
// Secrets will have to be provided later, on update
return nil, nil
}
secrets := req.GetCatalog().GetSecrets().AsMap()
persistedSecrets := map[string]interface{}{
constSecretValue: secrets[constSecretValue],
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
}
}
}
}
if _, ok := secrets[constSecretId]; ok {
persistedSecrets[constSecretId] = secrets[constSecretId]
if skipRotation {
return &pb.OnCreateCatalogResponse{
Persisted: &pb.HostCatalogPersisted{
Secrets: secrets,
},
}, nil
}
persist, err := structpb.NewStruct(persistedSecrets)
newPersisted, err := rotateCredFromCallback(ctx, catalog)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "failed marshaling persisted secrets: %q", err.Error())
return nil, fmt.Errorf("error formatting new persisted data: %w", err)
}
return &pb.OnCreateCatalogResponse{
Persisted: &pb.HostCatalogPersisted{
Secrets: persist,
},
}, nil
ret := &pb.OnCreateCatalogResponse{
Persisted: newPersisted,
}
return ret, nil
}
func (p *AzurePlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalogRequest) (*pb.OnUpdateCatalogResponse, error) {
if err := validateCatalog(req.GetNewCatalog()); err != nil {
return nil, err
newCatalog := req.GetNewCatalog()
if newCatalog == nil {
// Should never happen
return nil, errors.New("new catalog is nil")
}
currentCatalog := req.GetCurrentCatalog()
if currentCatalog == nil {
// Should never happen
return nil, status.Error(codes.FailedPrecondition, "current catalog is nil")
return nil, errors.New("current catalog is nil")
}
secrets := req.GetNewCatalog().GetSecrets()
secrets := newCatalog.GetSecrets()
if secrets == nil {
// If new secrets weren't passed in, don't rotate what we have on
// update.
return &pb.OnUpdateCatalogResponse{}, nil
}
if err := validateSecrets(secrets); err != nil {
return nil, err
//
// TODO: There may be combinations of attributes where those values
// changing (or being added) should trigger rotation.
return nil, nil
}
sanitizeSecretsFields(secrets)
secretsMap := secrets.AsMap()
persistedSecrets := map[string]interface{}{
constSecretValue: secretsMap[constSecretValue],
// 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
}
if _, ok := secretsMap[constSecretId]; ok {
persistedSecrets[constSecretId] = secretsMap[constSecretId]
if persisted.GetSecrets() == nil {
goto after_revocation
}
persist, err := structpb.NewStruct(persistedSecrets)
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())
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "failed marshaling persisted secrets: %q", err.Error())
// TODO: warn in some way?
goto after_revocation
}
if err := removeCredential(ctx, authConfig); err != nil {
// TODO: warn
goto after_revocation
}
return &pb.OnUpdateCatalogResponse{
Persisted: &pb.HostCatalogPersisted{
Secrets: persist,
},
}, nil
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
}
func (p *AzurePlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalogRequest) (*pb.OnDeleteCatalogResponse, error) {
if req.GetCatalog() == nil {
return nil, errors.New("catalog is nil")
}
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
}
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) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*pb.ListHostsResponse, error) {
if len(req.GetSets()) == 0 {
// Nothing to fetch
return &pb.ListHostsResponse{}, nil
return nil, nil
}
catalog := req.GetCatalog()
if catalog == nil {
return nil, status.Error(codes.FailedPrecondition, "catalog is nil")
return nil, errors.New("catalog is nil")
}
catalogAttrs := catalog.GetAttributes()
if catalogAttrs == nil {
return nil, status.Error(codes.FailedPrecondition, "catalog has no attributes")
return nil, errors.New("catalog has no attributes")
}
// Create an authorizer
catalog.Secrets = req.GetPersisted().GetSecrets()
authzInfo, err := getAuthorizationInfo(catalog)
if err != nil {
return nil, fmt.Errorf("error fetching authorizationInfo: %w", err)
var secrets *structpb.Struct
if req.GetPersisted() != nil {
secrets = req.GetPersisted().GetSecrets()
}
if authzInfo == nil {
return nil, status.Error(codes.FailedPrecondition, "authorization info is nil")
}
authorizer, err := authzInfo.autorestAuthorizer()
subscriptionId, authorizer, err := fetchAuthorizer(catalog, secrets)
if err != nil {
return nil, fmt.Errorf("error fetching autorest authorizer: %w", err)
return nil, fmt.Errorf("error fetching subscription id and authorizer: %w", err)
}
if subscriptionId == "" {
return nil, errors.New("subscription id not discovered")
}
if authorizer == nil {
return nil, errors.New("fetched authorizer is nil")
@@ -151,7 +260,7 @@ func (p *AzurePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (
commonOpts := make([]Option, 0, 3)
commonOpts = append(commonOpts,
WithSubscriptionId(authzInfo.AuthParams.SubscriptionId),
WithSubscriptionId(subscriptionId),
WithAuthorizer(authorizer),
)
@@ -275,11 +384,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 _, s := range *iv.Statuses {
if s.Code == nil {
for _, status := range *iv.Statuses {
if status.Code == nil {
continue
}
state := strings.ToLower(*s.Code)
state := strings.ToLower(*status.Code)
prefix := "powerstate/"
if !strings.HasPrefix(state, prefix) {
continue
@@ -395,78 +504,9 @@ func splitId(in, expectedService, expectedResource string) (string, string, erro
return splitId[3], splitId[7], nil
}
// 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))
func sanitizeSecretsFields(in *structpb.Struct) {
// Only we are allowed to set this value
if fields := in.GetFields(); fields != nil {
delete(fields, constCredsLastRotatedTime)
}
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 allowedFields = map[string]struct{}{
constDisableCredentialRotation: {},
constSubscriptionId: {},
constTenantId: {},
constClientId: {},
}
func validateCatalog(catalog *hostcatalogs.HostCatalog) error {
if catalog == nil {
return status.Error(codes.InvalidArgument, "catalog is nil")
}
var attrs Attributes
attrMap := catalog.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 := allowedFields[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
}
+118 -248
View File
@@ -15,7 +15,6 @@ 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"
)
@@ -47,251 +46,116 @@ func TestListHosts(t *testing.T) {
pretty.Println(lhResp.GetHosts())
}
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",
}),
},
{
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 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 TestCreateCatalog(t *testing.T) {
func TestOnCreateOnDeleteCatalog(t *testing.T) {
require := require.New(t)
ctx := context.Background()
p := &AzurePlugin{}
attrs := wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
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: "secret_value",
constSecretId: "secret_id",
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,
})
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())
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),
)
}()
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())
// 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())
secretsWithRotationTime := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
constCredsLastRotatedTime: "something",
// 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{},
})
res, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
Attributes: attrs,
Secrets: secretsWithRotationTime,
}})
assert.Error(t, err)
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,
},
})
require.NoError(err)
require.Nil(cdResp)
// Make sure the creds no longer work
// FIXME: Don't, for the same reason
// waitForCreds(t, &waitForCredsAuthConfig, false)
}
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 {
func waitForCreds(t *testing.T, hConfig *WrappedHamiltonConfig, shouldWork bool) auth.Authorizer {
t.Helper()
require := require.New(t)
ctx := context.Background()
@@ -300,9 +164,9 @@ func waitForCreds(t *testing.T, authzInfo *AuthorizationInfo, shouldWork bool) a
environments.Global,
auth.MsGraph,
auth.TokenVersion2,
authzInfo.AuthParams.TenantId,
authzInfo.AuthParams.ClientId,
authzInfo.AuthParams.SecretValue)
hConfig.TenantID,
hConfig.ClientID,
hConfig.ClientSecret)
require.NoError(err)
for {
@@ -366,39 +230,45 @@ 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) (*AuthorizationInfo, *msgraph.PasswordCredential, func()) {
func testCreateInitialSecret(t *testing.T, hostCatalog *hostcatalogs.HostCatalog) (string, *WrappedHamiltonConfig, *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
authzInfo, err := getAuthorizationInfo(hostCatalog)
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()))
require.NoError(err)
// Get the credential
initialCred, err = addCredential(ctx, authzInfo)
initialCred, err = addCredential(ctx, authConfig, WithObjectId(objId))
require.NoError(err)
require.NotNil(initialCred)
require.NotNil(initialCred.KeyId)
require.NotNil(initialCred.SecretText)
origAuthConfig := *authzInfo
origAuthConfig.AuthParams.SecretId = *initialCred.KeyId
origAuthConfig := *authConfig
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())
}
}
authzInfo.AuthParams.SecretId = *initialCred.KeyId
authzInfo.AuthParams.SecretValue = *initialCred.SecretText
authzInfo.HamiltonConfig.ClientSecret = *initialCred.SecretText
waitForCreds(t, authzInfo, true)
authConfig.SecretId = *initialCred.KeyId
authConfig.ClientSecret = *initialCred.SecretText
waitForCreds(t, authConfig, true)
return authzInfo, initialCred, cleanup
return objId, authConfig, initialCred, cleanup
}