From 4078417016037d29074f4925e734523f290576f9 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Tue, 18 Jun 2024 19:57:18 +0100 Subject: [PATCH 01/24] Audit: check if context is already cancelled when assessing viability for audit (#27531) * check if context is already cancelled when assessing viability for audit * changelog --- audit/broker.go | 8 ++++++++ audit/broker_test.go | 5 ++++- changelog/27531.txt | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 changelog/27531.txt diff --git a/audit/broker.go b/audit/broker.go index 2681ca0ede70..96cd1405f3d1 100644 --- a/audit/broker.go +++ b/audit/broker.go @@ -462,6 +462,8 @@ func (b *Broker) IsRegistered(name string) bool { // isContextViable examines the supplied context to see if its own deadline would // occur later than a newly created context with a specific timeout. +// Additionally, whether the supplied context is already cancelled, thus making it +// unviable. // If the existing context is viable it can be used 'as-is', if not, the caller // should consider creating a new context with the relevant deadline and associated // context values (e.g. namespace) in order to reduce the likelihood that the @@ -472,6 +474,12 @@ func isContextViable(ctx context.Context) bool { return false } + select { + case <-ctx.Done(): + return false + default: + } + deadline, hasDeadline := ctx.Deadline() // If there's no deadline on the context then we don't need to worry about diff --git a/audit/broker_test.go b/audit/broker_test.go index 074cb203f0e4..b6094322eafc 100644 --- a/audit/broker_test.go +++ b/audit/broker_test.go @@ -160,11 +160,14 @@ func BenchmarkAuditBroker_File_Request_DevNull(b *testing.B) { } // TestBroker_isContextViable_basics checks the expected result of isContextViable -// for basic inputs such as nil and a never-ending context. +// for basic inputs such as nil, cancelled context and a never-ending context. func TestBroker_isContextViable_basics(t *testing.T) { t.Parallel() require.False(t, isContextViable(nil)) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + require.False(t, isContextViable(ctx)) require.True(t, isContextViable(context.Background())) } diff --git a/changelog/27531.txt b/changelog/27531.txt new file mode 100644 index 000000000000..3dda984d1d7d --- /dev/null +++ b/changelog/27531.txt @@ -0,0 +1,5 @@ +```release-note:bug +core/audit: Audit logging a Vault request/response checks if the existing context +is cancelled and will now use a new context with a 5 second timeout. +If the existing context is cancelled a new context, will be used. +``` \ No newline at end of file From b48045fd98339c7a8386dc98b47a0f87469192f7 Mon Sep 17 00:00:00 2001 From: Robert <17119716+robmonte@users.noreply.github.com> Date: Tue, 18 Jun 2024 13:59:28 -0500 Subject: [PATCH 02/24] Add sync paths to restricted list (#27428) * Fix sync config path * Use full sync activate path --- website/content/partials/api/restricted-endpoints.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/content/partials/api/restricted-endpoints.mdx b/website/content/partials/api/restricted-endpoints.mdx index 41afeff099de..2dd6066c2338 100644 --- a/website/content/partials/api/restricted-endpoints.mdx +++ b/website/content/partials/api/restricted-endpoints.mdx @@ -6,6 +6,7 @@ API path | Root | Admin ------------------------------------------- | ---- | ----- +`sys/activation-flags/secrets-sync/activate`| YES | NO `sys/audit` | YES | NO `sys/audit-hash` | YES | YES `sys/config/auditing/*` | YES | NO @@ -54,6 +55,7 @@ API path | Root | Admin `sys/sealwrap/rewrap` | YES | NO `sys/step-down` | YES | NO `sys/storage` | YES | NO +`sys/sync/config | YES | YES `sys/unseal` | YES | NO Privileged CLI commands without public API endpoints: From 027888be7343624e8e20ba90278ab9380f2f0fe7 Mon Sep 17 00:00:00 2001 From: Mike Palmiotto Date: Tue, 18 Jun 2024 15:41:45 -0400 Subject: [PATCH 03/24] sdk: Bump grpc version to 1.64.0 (#27532) --- sdk/go.mod | 28 ++++++++-------- sdk/go.sum | 96 +++++++++++++++++++++--------------------------------- 2 files changed, 51 insertions(+), 73 deletions(-) diff --git a/sdk/go.mod b/sdk/go.mod index c9b8f7912b1f..9b9d8cf5e8e0 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -49,12 +49,12 @@ require ( golang.org/x/crypto v0.23.0 golang.org/x/net v0.25.0 golang.org/x/text v0.15.0 - google.golang.org/grpc v1.60.1 + google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.1 ) require ( - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute v1.25.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect @@ -65,17 +65,17 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/fatih/color v1.16.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/frankban/quicktest v1.14.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/s2a-go v0.1.4 // indirect + github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -107,19 +107,19 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/stretchr/objx v0.5.2 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/mod v0.11.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/term v0.20.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.10.0 // indirect - google.golang.org/api v0.134.0 // indirect + google.golang.org/api v0.169.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/sdk/go.sum b/sdk/go.sum index fbf9ce0cf9c9..d8c082dc47b4 100644 --- a/sdk/go.sum +++ b/sdk/go.sum @@ -21,8 +21,8 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/cloudsqlconn v1.4.3 h1:/WYFbB1NtMtoMxCbqpzzTFPDkxxlLTPme390KEGaEPc= cloud.google.com/go/cloudsqlconn v1.4.3/go.mod h1:QL3tuStVOO70txb3rs4G8j5uMfo5ztZii8K3oGD3VYA= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -58,7 +58,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= @@ -87,11 +86,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= @@ -117,8 +111,6 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= @@ -128,11 +120,10 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -148,8 +139,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= @@ -214,8 +205,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -227,22 +218,20 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/cap/ldap v0.0.0-20240328153749-fcfe271d0227 h1:R5CMNyBNZqODw2DcGaSa2X96AgtLotXsH7aOa07zTTI= @@ -490,7 +479,6 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= @@ -545,21 +533,20 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -588,7 +575,6 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= @@ -660,7 +646,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -685,8 +670,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -725,7 +710,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -767,8 +751,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -844,8 +828,8 @@ google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw= -google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk= +google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -878,7 +862,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -886,12 +869,11 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -907,12 +889,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -941,7 +920,6 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= From 66e78db4259500810c551cb09db3d1aa252f8cb0 Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Tue, 18 Jun 2024 14:20:22 -0600 Subject: [PATCH 04/24] Mask obfuscated Secret sync create/edit fields (#27348) * wip not working on edit view * changelog * vercel and fix tests * need conditional to not break all the things: * create test coverage and add for other obfustcaed fonts, still missing one. * Update 27348.txt * remove meep * comment * test coverage --- changelog/27348.txt | 3 ++ ui/app/models/sync/destinations/aws-sm.js | 4 ++ ui/app/models/sync/destinations/azure-kv.js | 2 + ui/app/models/sync/destinations/gcp-sm.js | 2 +- ui/app/models/sync/destinations/gh.js | 2 + .../sync/destinations/vercel-project.js | 2 + ui/lib/core/addon/components/form-field.hbs | 2 +- ui/lib/core/addon/components/masked-input.hbs | 2 +- .../sync/secrets/destination-test.js | 2 +- ui/tests/helpers/general-selectors.ts | 1 + ui/tests/helpers/sync/sync-selectors.js | 5 +++ .../page/destinations/create-and-edit-test.js | 42 ++++++++++++++++++- 12 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 changelog/27348.txt diff --git a/changelog/27348.txt b/changelog/27348.txt new file mode 100644 index 000000000000..ec7ece0b851a --- /dev/null +++ b/changelog/27348.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Mask obfuscated fields when creating/editing a Secrets sync destination. +``` diff --git a/ui/app/models/sync/destinations/aws-sm.js b/ui/app/models/sync/destinations/aws-sm.js index 4b38875a2616..5574d19a2e53 100644 --- a/ui/app/models/sync/destinations/aws-sm.js +++ b/ui/app/models/sync/destinations/aws-sm.js @@ -32,6 +32,8 @@ export default class SyncDestinationsAwsSecretsManagerModel extends SyncDestinat label: 'Access key ID', subText: 'Access key ID to authenticate against the secrets manager. If empty, Vault will use the AWS_ACCESS_KEY_ID environment variable if configured.', + sensitive: true, + noCopy: true, }) accessKeyId; // obfuscated, never returned by API @@ -39,6 +41,8 @@ export default class SyncDestinationsAwsSecretsManagerModel extends SyncDestinat label: 'Secret access key', subText: 'Secret access key to authenticate against the secrets manager. If empty, Vault will use the AWS_SECRET_ACCESS_KEY environment variable if configured.', + sensitive: true, + noCopy: true, }) secretAccessKey; // obfuscated, never returned by API diff --git a/ui/app/models/sync/destinations/azure-kv.js b/ui/app/models/sync/destinations/azure-kv.js index 42de9a173006..fa363d695909 100644 --- a/ui/app/models/sync/destinations/azure-kv.js +++ b/ui/app/models/sync/destinations/azure-kv.js @@ -55,6 +55,8 @@ export default class SyncDestinationsAzureKeyVaultModel extends SyncDestinationM @attr('string', { subText: 'Client secret of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_SECRET environment variable if configured.', + sensitive: true, + noCopy: true, }) clientSecret; // obfuscated, never returned by API diff --git a/ui/app/models/sync/destinations/gcp-sm.js b/ui/app/models/sync/destinations/gcp-sm.js index 03f70ac62254..6de8200eeb5d 100644 --- a/ui/app/models/sync/destinations/gcp-sm.js +++ b/ui/app/models/sync/destinations/gcp-sm.js @@ -37,7 +37,7 @@ export default class SyncDestinationsGoogleCloudSecretManagerModel extends SyncD editType: 'file', docLink: '/vault/docs/secrets/gcp#authentication', }) - credentials; // obfuscated, never returned by API + credentials; // obfuscated, never returned by API. Masking handled by EnableInput component @attr('object', { subText: diff --git a/ui/app/models/sync/destinations/gh.js b/ui/app/models/sync/destinations/gh.js index a8db62a9a804..8cd6e7bd147f 100644 --- a/ui/app/models/sync/destinations/gh.js +++ b/ui/app/models/sync/destinations/gh.js @@ -27,6 +27,8 @@ export default class SyncDestinationsGithubModel extends SyncDestinationModel { @attr('string', { subText: 'Personal access token to authenticate to the GitHub repository. If empty, Vault will use the GITHUB_ACCESS_TOKEN environment variable if configured.', + sensitive: true, + noCopy: true, }) accessToken; // obfuscated, never returned by API diff --git a/ui/app/models/sync/destinations/vercel-project.js b/ui/app/models/sync/destinations/vercel-project.js index aaa88f671c63..b788a780c41f 100644 --- a/ui/app/models/sync/destinations/vercel-project.js +++ b/ui/app/models/sync/destinations/vercel-project.js @@ -42,6 +42,8 @@ const formFieldGroups = [ export default class SyncDestinationsVercelProjectModel extends SyncDestinationModel { @attr('string', { subText: 'Vercel API access token with the permissions to manage environment variables.', + sensitive: true, + noCopy: true, }) accessToken; // obfuscated, never returned by API diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs index 2b9b3103ea91..84991d19e757 100644 --- a/ui/lib/core/addon/components/form-field.hbs +++ b/ui/lib/core/addon/components/form-field.hbs @@ -247,7 +247,7 @@ diff --git a/ui/lib/core/addon/components/masked-input.hbs b/ui/lib/core/addon/components/masked-input.hbs index eebc6d46f8c8..833b940a259d 100644 --- a/ui/lib/core/addon/components/masked-input.hbs +++ b/ui/lib/core/addon/components/masked-input.hbs @@ -32,7 +32,7 @@ aria-label={{or @name "masked input"}} {{on "change" this.onChange}} {{on "keyup" (fn this.handleKeyUp @name)}} - data-test-textarea + data-test-textarea={{or @name ""}} /> {{/if}} {{#if @allowCopy}} diff --git a/ui/tests/acceptance/sync/secrets/destination-test.js b/ui/tests/acceptance/sync/secrets/destination-test.js index 66404f0d1dd6..5101ab183617 100644 --- a/ui/tests/acceptance/sync/secrets/destination-test.js +++ b/ui/tests/acceptance/sync/secrets/destination-test.js @@ -86,7 +86,7 @@ module('Acceptance | sync | destination (singular)', function (hooks) { await visit('vault/sync/secrets/destinations/vercel-project/destination-vercel/edit'); await click(ts.enableField('accessToken')); - await fillIn(ts.inputByAttr('accessToken'), 'foobar'); + await fillIn(ts.maskedInput('accessToken'), 'foobar'); await click(ts.saveButton); await click(ts.toolbar('Edit destination')); await click(ts.saveButton); diff --git a/ui/tests/helpers/general-selectors.ts b/ui/tests/helpers/general-selectors.ts index 674a5bb51a6d..9a0421492722 100644 --- a/ui/tests/helpers/general-selectors.ts +++ b/ui/tests/helpers/general-selectors.ts @@ -79,4 +79,5 @@ export const GENERAL = { navLink: (label: string) => `[data-test-sidebar-nav-link="${label}"]`, cancelButton: '[data-test-cancel]', saveButton: '[data-test-save]', + maskedInput: (name: string) => `[data-test-textarea="${name}"]`, }; diff --git a/ui/tests/helpers/sync/sync-selectors.js b/ui/tests/helpers/sync/sync-selectors.js index 842ca92e7bdc..db3658f9a3ee 100644 --- a/ui/tests/helpers/sync/sync-selectors.js +++ b/ui/tests/helpers/sync/sync-selectors.js @@ -97,6 +97,11 @@ export const PAGE = { case 'customTags': await fillIn('[data-test-kv-key="0"]', 'foo'); return fillIn('[data-test-kv-value="0"]', value); + case 'accessKeyId': + case 'secretAccessKey': + case 'clientSecret': + case 'accessToken': + return fillIn(GENERAL.maskedInput(attr), value); case 'deploymentEnvironments': await click('[data-test-input="deploymentEnvironments"] input#development'); await click('[data-test-input="deploymentEnvironments"] input#preview'); diff --git a/ui/tests/integration/components/sync/secrets/page/destinations/create-and-edit-test.js b/ui/tests/integration/components/sync/secrets/page/destinations/create-and-edit-test.js index c5d271163fb0..46c86a0398cc 100644 --- a/ui/tests/integration/components/sync/secrets/page/destinations/create-and-edit-test.js +++ b/ui/tests/integration/components/sync/secrets/page/destinations/create-and-edit-test.js @@ -10,7 +10,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support'; import hbs from 'htmlbars-inline-precompile'; import sinon from 'sinon'; import { Response } from 'miragejs'; -import { click, render, typeIn } from '@ember/test-helpers'; +import { click, fillIn, render, typeIn } from '@ember/test-helpers'; import { PAGE } from 'vault/tests/helpers/sync/sync-selectors'; import { syncDestinations } from 'vault/helpers/sync-destinations'; import { decamelize, underscore } from '@ember/string'; @@ -131,6 +131,28 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE await click(PAGE.saveButton); }); + test('edit: payload only contains masked inputs when they have changed', async function (assert) { + assert.expect(1); + this.model = this.generateModel(); + + this.server.patch(`sys/sync/destinations/${this.model.type}/${this.model.name}`, (schema, req) => { + const payload = JSON.parse(req.requestBody); + assert.propEqual( + payload, + { secret_access_key: 'new-secret' }, + 'payload contains the changed obfuscated field' + ); + return { payload }; + }); + + await this.renderFormComponent(); + await click(PAGE.enableField('accessKeyId')); + await click(PAGE.maskedInput('accessKeyId')); // click on input but do not change value + await click(PAGE.enableField('secretAccessKey')); + await fillIn(PAGE.maskedInput('secretAccessKey'), 'new-secret'); + await click(PAGE.saveButton); + }); + test('it renders API errors', async function (assert) { assert.expect(1); const name = 'my-failed-dest'; @@ -192,6 +214,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE // CREATE FORM ASSERTIONS FOR EACH DESTINATION TYPE for (const destination of SYNC_DESTINATIONS) { const { name, type } = destination; + const obfuscatedFields = ['accessToken', 'clientSecret', 'secretAccessKey', 'accessKeyId']; module(`create destination: ${type}`, function (hooks) { hooks.beforeEach(function () { @@ -209,6 +232,23 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE } }); + test('it masks obfuscated fields', async function (assert) { + const filteredObfuscatedFields = this.model.formFields.filter((field) => + obfuscatedFields.includes(field.name) + ); + assert.expect(filteredObfuscatedFields.length); + await this.renderFormComponent(); + // iterate over the form fields and filter for those that are obfuscated + // fill those in and assert that they are masked + filteredObfuscatedFields.forEach(async (field) => { + await fillIn(PAGE.maskedInput(field.name), 'blah'); + + assert + .dom(PAGE.maskedInput(field.name)) + .hasClass('masked-font', `it renders ${field.name} for ${destination} with masked font`); + }); + }); + test('it saves destination and transitions to details', async function (assert) { assert.expect(5); const name = 'my-name'; From ab08d623e8c6da3bca79cf046b7ca759fc771400 Mon Sep 17 00:00:00 2001 From: John-Michael Faircloth Date: Wed, 19 Jun 2024 08:35:22 -0500 Subject: [PATCH 05/24] docs: add note to jwt auth for bound aud changes (#27530) --- website/content/docs/auth/jwt/index.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/content/docs/auth/jwt/index.mdx b/website/content/docs/auth/jwt/index.mdx index 12746e776fa9..06961853e502 100644 --- a/website/content/docs/auth/jwt/index.mdx +++ b/website/content/docs/auth/jwt/index.mdx @@ -10,6 +10,12 @@ description: >- @include 'x509-sha1-deprecation.mdx' +~> **Note**: Starting in Vault 1.17, if the JWT in the authentication request +contains an `aud` claim, the associated `bound_audiences` for the "jwt" role +must match at least one of the `aud` claims declared for the JWT. For +additional details, refer to the [JWT auth method (API)](/vault/api-docs/auth/jwt) +documentation and [1.17 Upgrade Guide](/vault/docs/upgrading/upgrade-to-1.17.x#jwt-auth-login-requires-bound-audiences-on-the-role). + The `jwt` auth method can be used to authenticate with Vault using [OIDC](https://en.wikipedia.org/wiki/OpenID_Connect) or by providing a [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token). From e7f2107b521d8e0ca9c8699fe156ca2b38d3f903 Mon Sep 17 00:00:00 2001 From: Adrian Todorov Date: Wed, 19 Jun 2024 15:56:45 +0200 Subject: [PATCH 06/24] clarify the JWT auth bound_audiences change in behaviour (#27541) --- website/content/docs/upgrading/upgrade-to-1.17.x.mdx | 3 ++- .../partials/known-issues/1_16-jwt_auth_bound_audiences.mdx | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/website/content/docs/upgrading/upgrade-to-1.17.x.mdx b/website/content/docs/upgrading/upgrade-to-1.17.x.mdx index 512097179dc6..dfe738d1509e 100644 --- a/website/content/docs/upgrading/upgrade-to-1.17.x.mdx +++ b/website/content/docs/upgrading/upgrade-to-1.17.x.mdx @@ -70,7 +70,8 @@ The beta request limiter will be removed from Vault entirely in a later release. ### JWT auth login requires bound audiences on the role -The `bound_audiences` parameter of "jwt" roles **must** match at least one of +The `bound_audiences` parameter of "jwt" roles is **mandatory** if the JWT contains an audience +(which is more often than not the case), and **must** match at least one of the JWT's associated `aud` claims. The `aud` claim claim can be a single string or a list of strings as per [RFC 7519 Section 4.1.3](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3). If the JWT's `aud` claim is not set, then the role's `bound_audiences` diff --git a/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx b/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx index 629eed95fe3a..0dbd75ec9182 100644 --- a/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx +++ b/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx @@ -14,8 +14,8 @@ the versions after 1.15.10 and 1.16.4. However, the behavior change will go into effect in 1.17. The new behavior requires that the `bound_audiences` parameter of "jwt" roles -**must** match at least one of the JWT's associated `aud` claims. The `aud` -claim can be a single string or a list of strings as per +**must** be set and **must** match at least one of the JWT's associated `aud` claims if there are any. +The `aud` claim can be a single string or a list of strings as per [RFC 7519 Section 4.1.3](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3). Users may not be able to log into Vault if the JWT role is configured From 395972289277bf76620457fc590c03ee6524fefe Mon Sep 17 00:00:00 2001 From: Violet Hynes Date: Wed, 19 Jun 2024 10:23:51 -0400 Subject: [PATCH 07/24] VAULT-28192 fix Agent and Proxy consuming large amounts of CPU for auto-auth self-healing (#27518) * VAULT-28192 fix Agent and Proxy consuming large amounts of CPU for auto-auth self-healing * Changelog * Update changelog * drain incoming if we get invalid token --------- Co-authored-by: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> --- changelog/27518.txt | 7 +++++ command/agent/template/template.go | 39 +++++++++++---------------- command/agentproxyshared/auth/auth.go | 12 +++------ 3 files changed, 26 insertions(+), 32 deletions(-) create mode 100644 changelog/27518.txt diff --git a/changelog/27518.txt b/changelog/27518.txt new file mode 100644 index 000000000000..c8412c62b02e --- /dev/null +++ b/changelog/27518.txt @@ -0,0 +1,7 @@ +```release-note:bug +agent: Fixed an issue causing excessive CPU usage during normal operation +``` + +```release-note:bug +proxy: Fixed an issue causing excessive CPU usage during normal operation +``` \ No newline at end of file diff --git a/command/agent/template/template.go b/command/agent/template/template.go index 402b5f50fc8b..f0619694a597 100644 --- a/command/agent/template/template.go +++ b/command/agent/template/template.go @@ -246,31 +246,24 @@ func (ts *Server) Run(ctx context.Context, incoming chan string, templates []*ct ts.runner.Stop() return nil } - default: - // We are using default instead of a new case block to prioritize the case where <-incoming has a new value over - // receiving an error message from the consul-template server - select { - case err := <-ts.runner.ServerErrCh: - var responseError *api.ResponseError - ok := errors.As(err, &responseError) - if !ok { - ts.logger.Error("template server: could not extract error response") - continue - } - if responseError.StatusCode == 403 && strings.Contains(responseError.Error(), logical.ErrInvalidToken.Error()) && !tokenRenewalInProgress.Load() { - ts.logger.Info("template server: received invalid token error") - - // Drain the error channel before sending a new error - select { - case <-invalidTokenCh: - default: - } - invalidTokenCh <- err - } - default: + case err := <-ts.runner.ServerErrCh: + var responseError *api.ResponseError + ok := errors.As(err, &responseError) + if !ok { + ts.logger.Error("template server: could not extract error response") continue } - + if responseError.StatusCode == 403 && strings.Contains(responseError.Error(), logical.ErrInvalidToken.Error()) && !tokenRenewalInProgress.Load() { + ts.logger.Info("template server: received invalid token error") + + // Drain the error channel and incoming channel before sending a new error + select { + case <-invalidTokenCh: + case <-incoming: + default: + } + invalidTokenCh <- err + } } } } diff --git a/command/agentproxyshared/auth/auth.go b/command/agentproxyshared/auth/auth.go index bcfbf3be9c5e..96f5026cbb92 100644 --- a/command/agentproxyshared/auth/auth.go +++ b/command/agentproxyshared/auth/auth.go @@ -563,18 +563,12 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) error { // Set authenticated when authentication succeeds metrics.SetGauge([]string{ah.metricsSignifier, "authenticated"}, 1) ah.logger.Info("renewed auth token") - case <-credCh: ah.logger.Info("auth method found new credentials, re-authenticating") break LifetimeWatcherLoop - default: - select { - case <-ah.InvalidToken: - ah.logger.Info("invalid token found, re-authenticating") - break LifetimeWatcherLoop - default: - continue - } + case <-ah.InvalidToken: + ah.logger.Info("invalid token found, re-authenticating") + break LifetimeWatcherLoop } } } From 46a41a549b54e2037ae97ef02648c8e87b4bc46b Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Wed, 19 Jun 2024 16:51:24 -0400 Subject: [PATCH 08/24] Specify headers by environment variable (#21993) * Specify headers by environment var * Add changelog entry * Add tests, docs * Formatting --------- Co-authored-by: Violet Hynes --- api/client.go | 26 ++++++++++++ api/client_test.go | 55 +++++++++++++++++++++++++ changelog/21993.txt | 3 ++ website/content/docs/commands/index.mdx | 6 +++ 4 files changed, 90 insertions(+) create mode 100644 changelog/21993.txt diff --git a/api/client.go b/api/client.go index 17356579368c..0090321caa7f 100644 --- a/api/client.go +++ b/api/client.go @@ -10,6 +10,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/hex" + "encoding/json" "fmt" "net" "net/http" @@ -41,6 +42,7 @@ const ( EnvVaultClientCert = "VAULT_CLIENT_CERT" EnvVaultClientKey = "VAULT_CLIENT_KEY" EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT" + EnvVaultHeaders = "VAULT_HEADERS" EnvVaultSRVLookup = "VAULT_SRV_LOOKUP" EnvVaultSkipVerify = "VAULT_SKIP_VERIFY" EnvVaultNamespace = "VAULT_NAMESPACE" @@ -665,6 +667,30 @@ func NewClient(c *Config) (*Client, error) { client.setNamespace(namespace) } + if envHeaders := os.Getenv(EnvVaultHeaders); envHeaders != "" { + var result map[string]any + err := json.Unmarshal([]byte(envHeaders), &result) + if err != nil { + return nil, fmt.Errorf("could not unmarshal environment-supplied headers") + } + var forbiddenHeaders []string + for key, value := range result { + if strings.HasPrefix(key, "X-Vault-") { + forbiddenHeaders = append(forbiddenHeaders, key) + continue + } + + value, ok := value.(string) + if !ok { + return nil, fmt.Errorf("environment-supplied headers include non-string values") + } + client.AddHeader(key, value) + } + if len(forbiddenHeaders) > 0 { + return nil, fmt.Errorf("failed to setup Headers[%s]: Header starting by 'X-Vault-' are for internal usage only", strings.Join(forbiddenHeaders, ", ")) + } + } + return client, nil } diff --git a/api/client_test.go b/api/client_test.go index 3d75aabd6bda..1ed4dfd3d359 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -374,6 +374,61 @@ func TestDefaulRetryPolicy(t *testing.T) { } } +func TestClientEnvHeaders(t *testing.T) { + oldHeaders := os.Getenv(EnvVaultHeaders) + + defer func() { + os.Setenv(EnvVaultHeaders, oldHeaders) + }() + + cases := []struct { + Input string + Valid bool + }{ + { + "{}", + true, + }, + { + "{\"foo\": \"bar\"}", + true, + }, + { + "{\"foo\": 1}", // Values must be strings + false, + }, + { + "{\"X-Vault-Foo\": \"bar\"}", // X-Vault-* not allowed + false, + }, + } + + for _, tc := range cases { + os.Setenv(EnvVaultHeaders, tc.Input) + config := DefaultConfig() + config.ReadEnvironment() + _, err := NewClient(config) + if err != nil { + if tc.Valid { + t.Fatalf("unexpected error reading headers from environment: %v", err) + } + } else { + if !tc.Valid { + t.Fatal("no error reading headers from environment when error was expected") + } + } + } + + os.Setenv(EnvVaultHeaders, "{\"foo\": \"bar\"}") + config := DefaultConfig() + config.ReadEnvironment() + cli, _ := NewClient(config) + + if !reflect.DeepEqual(cli.Headers().Values("foo"), []string{"bar"}) { + t.Error("Environment-supplied headers not set in CLI client") + } +} + func TestClientEnvSettings(t *testing.T) { cwd, _ := os.Getwd() diff --git a/changelog/21993.txt b/changelog/21993.txt new file mode 100644 index 000000000000..856cfc9662e0 --- /dev/null +++ b/changelog/21993.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Allow vault CLI HTTP headers to be specified using the JSON-encoded VAULT_HEADERS environment variable +``` \ No newline at end of file diff --git a/website/content/docs/commands/index.mdx b/website/content/docs/commands/index.mdx index 7632dd452cb6..15109f9c8f26 100644 --- a/website/content/docs/commands/index.mdx +++ b/website/content/docs/commands/index.mdx @@ -439,6 +439,12 @@ Prevents the Vault client from following redirects. By default, the Vault client ~> **Note:** Disabling redirect following behavior could cause issues with commands such as 'vault operator raft snapshot' as this command redirects the request to the cluster's primary node. +### `VAULT_HEADERS` + +JSON-encoded headers to include in Vault HTTP requests performed by the CLI. For example: `{"FOO": "BAR"}`. + +Like the `-header` CLI parameter, headers starting with `X-Vault-` are forbidden. + ## Flags There are different CLI flags that are available depending on subcommands. Some From 36affc2fd2ffbef1f247b781bac0bea75d70c6e6 Mon Sep 17 00:00:00 2001 From: miagilepner Date: Thu, 20 Jun 2024 11:58:40 +0200 Subject: [PATCH 09/24] VAULT-28024: Current month new clients by namespace and mount (#27527) --- vault/activity_log_util_common.go | 148 +++-- vault/activity_log_util_common_test.go | 863 ++++++++++++++++++++++++- 2 files changed, 951 insertions(+), 60 deletions(-) diff --git a/vault/activity_log_util_common.go b/vault/activity_log_util_common.go index 952e06ead46b..ff8923a9580c 100644 --- a/vault/activity_log_util_common.go +++ b/vault/activity_log_util_common.go @@ -114,59 +114,131 @@ func (a *ActivityLog) computeCurrentMonthForBillingPeriodInternal(ctx context.Co if len(byMonth) > 1 { return nil, errors.New(fmt.Sprintf("multiple months of data found in partial month's client count breakdowns: %+v\n", byMonth)) } + totalCounts := &activity.CountsRecord{} + newCounts := &activity.CountsRecord{} + newNamespaces := make([]*activity.MonthlyNamespaceRecord, 0) + totalNamespaces := make([]*activity.MonthlyNamespaceRecord, 0) + + monthRecord := &activity.MonthRecord{ + Timestamp: timeutil.StartOfMonth(endTime).UTC().Unix(), + Counts: totalCounts, + Namespaces: totalNamespaces, + NewClients: &activity.NewClientRecord{ + Counts: newCounts, + Namespaces: newNamespaces, + }, + } + if len(byMonth) == 0 { + return monthRecord, nil + } - // Now we will add the clients for the current month to a copy of the billing period's hll to - // see how the cardinality grows. + // Create a copy of the previous month's HLL per client type + // We'll add all of the new clients seen this month to these typed HLLs hllByType := make(map[string]*hyperloglog.Sketch, len(ActivityClientTypes)) - totalByType := make(map[string]int, len(ActivityClientTypes)) for _, typ := range ActivityClientTypes { hllByType[typ] = billingPeriodHLL.Clone() } - for _, month := range byMonth { - if month.NewClients == nil || month.NewClients.Counts == nil || month.Counts == nil { - return nil, errors.New("malformed current month used to calculate current month's activity") - } + // This is the estimated number of clients seen in the previous months + seenPreviousMonthsEstimate := billingPeriodHLL.Estimate() - for _, typ := range ActivityClientTypes { - // Note that the following calculations assume that all clients seen are currently in - // the NewClients section of byMonth. It is best to explicitly check this, just verify - // our assumptions about the passed in byMonth argument. - if month.Counts.countByType(typ) != month.NewClients.Counts.countByType(typ) { - return nil, errors.New("current month clients cache assumes billing period") + runningMountEstimatesByType := make(map[string]uint64) + runningNSEstimatesByType := make(map[string]uint64) + + // we've already checked that byMonth has length of 1, so this will get the + // value of the only entry in the map + var month *processMonth + for _, month = range byMonth { + } + + // This month should have clients + if month.NewClients == nil || month.NewClients.Counts == nil || month.Counts == nil { + return nil, errors.New("malformed current month used to calculate current month's activity") + } + + for nsID, namespace := range month.Namespaces { + namespaceActivity := &activity.MonthlyNamespaceRecord{NamespaceID: nsID, Counts: &activity.CountsRecord{}} + newNamespaceActivity := &activity.MonthlyNamespaceRecord{NamespaceID: nsID, Counts: &activity.CountsRecord{}} + mountsActivity := make([]*activity.MountRecord, 0) + newMountsActivity := make([]*activity.MountRecord, 0) + + for mountAccessor, mount := range namespace.Mounts { + mountPath := a.mountAccessorToMountPath(mountAccessor) + + mountCounts := &activity.CountsRecord{} + newMountCounts := &activity.CountsRecord{} + + for _, typ := range ActivityClientTypes { + for clientID := range mount.Counts.clientsByType(typ) { + hllByType[typ].Insert([]byte(clientID)) + + // increment the per mount, per namespace, and total counts + // for each client + a.incrementCount(totalCounts, 1, typ) + a.incrementCount(mountCounts, 1, typ) + a.incrementCount(namespaceActivity.Counts, 1, typ) + } + + // get an estimated number of new clients on this mount and + // increment the new mount counts + mountEstimate := hllByType[typ].Estimate() - seenPreviousMonthsEstimate - runningMountEstimatesByType[typ] + runningMountEstimatesByType[typ] += mountEstimate + a.incrementCount(newMountCounts, int(mountEstimate), typ) } - for clientID := range month.NewClients.Counts.clientsByType(typ) { - // All the clients for the current month are in the newClients section, initially. - // We need to deduplicate these clients across the billing period by adding them - // into the billing period hyperloglogs. - hllByType[typ].Insert([]byte(clientID)) - totalByType[typ] += 1 + + // if there are any new clients on this mount, add it to the + // list of new mounts + if newMountCounts.HasCounts() { + newMountsActivity = append(newMountsActivity, &activity.MountRecord{MountPath: mountPath, Counts: newMountCounts}) } + mountsActivity = append(mountsActivity, &activity.MountRecord{MountPath: mountPath, Counts: mountCounts}) + } + + namespaceActivity.Mounts = mountsActivity + totalNamespaces = append(totalNamespaces, namespaceActivity) + + for _, typ := range ActivityClientTypes { + // now that we've added all the clients from the namespace, get an + // estimated number of new clients on the namespace and increment + // the new namespace counts + nsEstimate := hllByType[typ].Estimate() - seenPreviousMonthsEstimate - runningNSEstimatesByType[typ] + runningNSEstimatesByType[typ] += nsEstimate + a.incrementCount(newNamespaceActivity.Counts, int(nsEstimate), typ) + } + + // if there are any new clients on this namespace, add it to the + // list of new namespaces + if newNamespaceActivity.Counts.HasCounts() { + newNamespaceActivity.Mounts = newMountsActivity + newNamespaces = append(newNamespaces, newNamespaceActivity) } } - currentMonthNewByType := make(map[string]int, len(ActivityClientTypes)) + // get the overall estimated number of new clients and increment the + // total new counts for _, typ := range ActivityClientTypes { - // The number of new entities for the current month is approximately the size of the hll with - // the current month's entities minus the size of the initial billing period hll. - currentMonthNewByType[typ] = int(hllByType[typ].Estimate() - billingPeriodHLL.Estimate()) + totalEstimate := int(hllByType[typ].Estimate() - seenPreviousMonthsEstimate) + a.incrementCount(newCounts, totalEstimate, typ) } - return &activity.MonthRecord{ - Timestamp: timeutil.StartOfMonth(endTime).UTC().Unix(), - NewClients: &activity.NewClientRecord{Counts: &activity.CountsRecord{ - EntityClients: currentMonthNewByType[entityActivityType], - NonEntityClients: currentMonthNewByType[nonEntityTokenActivityType], - SecretSyncs: currentMonthNewByType[secretSyncActivityType], - ACMEClients: currentMonthNewByType[ACMEActivityType], - }}, - Counts: &activity.CountsRecord{ - EntityClients: totalByType[entityActivityType], - NonEntityClients: totalByType[nonEntityTokenActivityType], - SecretSyncs: totalByType[secretSyncActivityType], - ACMEClients: totalByType[ACMEActivityType], - }, - }, nil + monthRecord.Namespaces = totalNamespaces + monthRecord.NewClients.Namespaces = newNamespaces + return monthRecord, nil +} + +// incrementCount modifies the passed-in counts record by incrementing the +// client type's field by num +func (a *ActivityLog) incrementCount(c *activity.CountsRecord, num int, typ string) { + switch typ { + case entityActivityType: + c.EntityClients += num + case nonEntityTokenActivityType: + c.NonEntityClients += num + case secretSyncActivityType: + c.SecretSyncs += num + case ACMEActivityType: + c.ACMEClients += num + } } // sortALResponseNamespaces sorts the namespaces for activity log responses. diff --git a/vault/activity_log_util_common_test.go b/vault/activity_log_util_common_test.go index 91b064aa4c35..48a3e8dea43b 100644 --- a/vault/activity_log_util_common_test.go +++ b/vault/activity_log_util_common_test.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "sort" "testing" "time" @@ -18,15 +19,31 @@ import ( "google.golang.org/protobuf/proto" ) -// Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal creates 3 months -// of hyperloglogs and fills them with overlapping clients. The test calls -// computeCurrentMonthForBillingPeriodInternal with the current month map having -// some overlap with the previous months. The test then verifies that the -// results have the correct number of entity, non-entity, and secret sync -// association clients. The test also calls -// computeCurrentMonthForBillingPeriodInternal with an empty current month map, -// and verifies that the results are all 0. -func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) { +// equalActivityMonthRecords is a helper to sort the namespaces and mounts +// in activity.MonthRecord's, then compare their equality +func equalActivityMonthRecords(t *testing.T, expected, got *activity.MonthRecord) { + t.Helper() + sortNamespaces := func(namespaces []*activity.MonthlyNamespaceRecord) { + sort.SliceStable(namespaces, func(i, j int) bool { + return namespaces[i].NamespaceID < namespaces[j].NamespaceID + }) + for _, namespace := range namespaces { + sort.SliceStable(namespace.Mounts, func(i, j int) bool { + return namespace.Mounts[i].MountPath < namespace.Mounts[j].MountPath + }) + } + } + sortNamespaces(expected.Namespaces) + sortNamespaces(expected.NewClients.Namespaces) + sortNamespaces(got.Namespaces) + sortNamespaces(got.NewClients.Namespaces) + require.Equal(t, expected, got) +} + +// mockHLLGetter is a helper that returns an HLL getter function with 3 months +// of HLLs +func mockHLLGetter(t *testing.T) func(ctx context.Context, startTime time.Time) (*hyperloglog.Sketch, error) { + t.Helper() // populate the first month with clients 1-20 monthOneHLL := hyperloglog.New() // populate the second month with clients 10-30 @@ -46,7 +63,7 @@ func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) monthThreeHLL.Insert(clientID) } } - mockHLLGetFunc := func(ctx context.Context, startTime time.Time) (*hyperloglog.Sketch, error) { + return func(ctx context.Context, startTime time.Time) (*hyperloglog.Sketch, error) { currMonthStart := timeutil.StartOfMonth(time.Now()) if startTime.Equal(timeutil.MonthsPreviousTo(3, currMonthStart)) { return monthThreeHLL, nil @@ -59,7 +76,19 @@ func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) } return nil, fmt.Errorf("bad start time") } +} +// Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal creates 3 months +// of hyperloglogs and fills them with overlapping clients. The test calls +// computeCurrentMonthForBillingPeriodInternal with the current month map having +// some overlap with the previous months. The test then verifies that the +// results have the correct number of entity, non-entity, and secret sync +// association clients. The test also calls +// computeCurrentMonthForBillingPeriodInternal with an empty current month map, +// and verifies that the results are all 0. +func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) { + mockHLLGetFunc := mockHLLGetter(t) + month := newProcessMonth() // Below we register the entity, non-entity, and secret sync clients that // are seen in the current month @@ -81,6 +110,9 @@ func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) "client_41": {}, "client_42": {}, } + for id := range entitiesStruct { + month.add(&activity.EntityRecord{ClientID: id, ClientType: entityActivityType}) + } // We will add 3 nonentity clients from month 1 (clients 2,3,4), // 3 shared by months 1 and 2 (12,13,14), @@ -105,6 +137,9 @@ func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) "client_45": {}, "client_46": {}, } + for id := range nonEntitiesStruct { + month.add(&activity.EntityRecord{ClientID: id, ClientType: nonEntityTokenActivityType}) + } // secret syncs have 1 client from month 1 (5) // 1 shared by months 1 and 2 (15) @@ -120,24 +155,16 @@ func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) "client_47": {}, "client_48": {}, } - - counts := &processCounts{ - ClientsByType: map[string]clientIDSet{ - entityActivityType: entitiesStruct, - nonEntityTokenActivityType: nonEntitiesStruct, - secretSyncActivityType: secretSyncStruct, - }, + for id := range secretSyncStruct { + month.add(&activity.EntityRecord{ClientID: id, ClientType: secretSyncActivityType}) } currentMonthClientsMap := make(map[int64]*processMonth, 1) - currentMonthClients := &processMonth{ - Counts: counts, - NewClients: &processNewClients{Counts: counts}, - } + // Technially I think currentMonthClientsMap should have the keys as // unix timestamps, but for the purposes of the unit test it doesn't // matter what the values actually are. - currentMonthClientsMap[0] = currentMonthClients + currentMonthClientsMap[0] = month core, _, _ := TestCoreUnsealed(t) a := core.activityLog @@ -171,6 +198,798 @@ func Test_ActivityLog_ComputeCurrentMonthForBillingPeriodInternal(t *testing.T) require.Equal(t, &activity.CountsRecord{}, monthRecord.NewClients.Counts) } +// Test_ActivityLog_ComputeCurrentMonth_NamespaceMounts checks that current +// month counts are combined correctly with 3 previous months of data. The test +// checks a variety of scenarios with different mount and namespace values +func Test_ActivityLog_ComputeCurrentMonth_NamespaceMounts(t *testing.T) { + testCases := []struct { + name string + currentMonthClients []*activity.EntityRecord + wantErr bool + wantMonth *activity.MonthRecord + }{ + { + name: "no clients", + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{}, + Namespaces: []*activity.MonthlyNamespaceRecord{}, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{}, + Namespaces: []*activity.MonthlyNamespaceRecord{}, + }, + }, + }, + + { + // all the clients have been seen before + // they're all on ns1/mount1 + name: "all repeated", + currentMonthClients: []*activity.EntityRecord{ + {ClientID: "client_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_3", ClientType: secretSyncActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_4", ClientType: ACMEActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + }, + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 1, + SecretSyncs: 1, + NonEntityClients: 1, + ACMEClients: 1, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + }, + }, + }, + }, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{}, + Namespaces: []*activity.MonthlyNamespaceRecord{}, + }, + }, + }, + { + // all the clients have been seen before + // they're all on ns1, but mounts 1-4 + name: "all repeated multiple mounts", + currentMonthClients: []*activity.EntityRecord{ + {ClientID: "client_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns1", MountAccessor: "mount2"}, + {ClientID: "client_3", ClientType: secretSyncActivityType, NamespaceID: "ns1", MountAccessor: "mount3"}, + {ClientID: "client_4", ClientType: ACMEActivityType, NamespaceID: "ns1", MountAccessor: "mount4"}, + }, + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 1, + SecretSyncs: 1, + NonEntityClients: 1, + ACMEClients: 1, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + { + MountPath: "mount2", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + { + MountPath: "mount3", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + { + MountPath: "mount4", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{}, + Namespaces: []*activity.MonthlyNamespaceRecord{}, + }, + }, + }, + { + // all the clients have been seen before + // they're on namespaces ns1-4 + name: "all repeated multiple namespaces", + currentMonthClients: []*activity.EntityRecord{ + {ClientID: "client_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns2", MountAccessor: "mount1"}, + {ClientID: "client_3", ClientType: secretSyncActivityType, NamespaceID: "ns3", MountAccessor: "mount1"}, + {ClientID: "client_4", ClientType: ACMEActivityType, NamespaceID: "ns4", MountAccessor: "mount1"}, + }, + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 1, + SecretSyncs: 1, + NonEntityClients: 1, + ACMEClients: 1, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns2", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns3", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + }, + }, + { + NamespaceID: "ns4", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{}, + Namespaces: []*activity.MonthlyNamespaceRecord{}, + }, + }, + }, + { + // 4 clients that have been seen before on namespaces 1-4 + // 4 new clients on namespaces 1-4 + name: "new clients multiple namespaces", + currentMonthClients: []*activity.EntityRecord{ + {ClientID: "client_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns2", MountAccessor: "mount1"}, + {ClientID: "client_3", ClientType: secretSyncActivityType, NamespaceID: "ns3", MountAccessor: "mount1"}, + {ClientID: "client_4", ClientType: ACMEActivityType, NamespaceID: "ns4", MountAccessor: "mount1"}, + {ClientID: "new_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "new_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns2", MountAccessor: "mount1"}, + {ClientID: "new_3", ClientType: secretSyncActivityType, NamespaceID: "ns3", MountAccessor: "mount1"}, + {ClientID: "new_4", ClientType: ACMEActivityType, NamespaceID: "ns4", MountAccessor: "mount1"}, + }, + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 2, + SecretSyncs: 2, + NonEntityClients: 2, + ACMEClients: 2, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 2, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 2, + }, + }, + }, + }, + { + NamespaceID: "ns2", + Counts: &activity.CountsRecord{ + NonEntityClients: 2, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + NonEntityClients: 2, + }, + }, + }, + }, + { + NamespaceID: "ns3", + Counts: &activity.CountsRecord{ + SecretSyncs: 2, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + SecretSyncs: 2, + }, + }, + }, + }, + { + NamespaceID: "ns4", + Counts: &activity.CountsRecord{ + ACMEClients: 2, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + ACMEClients: 2, + }, + }, + }, + }, + }, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns2", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns3", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + }, + }, + { + NamespaceID: "ns4", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + }, + }, + }, + { + // 4 clients that have been seen before on ns1/mount1-4 + // 4 new clients on ns1/mount1-4 + name: "new clients multiple mounts", + currentMonthClients: []*activity.EntityRecord{ + {ClientID: "client_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns1", MountAccessor: "mount2"}, + {ClientID: "client_3", ClientType: secretSyncActivityType, NamespaceID: "ns1", MountAccessor: "mount3"}, + {ClientID: "client_4", ClientType: ACMEActivityType, NamespaceID: "ns1", MountAccessor: "mount4"}, + {ClientID: "new_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "new_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns1", MountAccessor: "mount2"}, + {ClientID: "new_3", ClientType: secretSyncActivityType, NamespaceID: "ns1", MountAccessor: "mount3"}, + {ClientID: "new_4", ClientType: ACMEActivityType, NamespaceID: "ns1", MountAccessor: "mount4"}, + }, + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 2, + SecretSyncs: 2, + NonEntityClients: 2, + ACMEClients: 2, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 2, + }, + }, + { + MountPath: "mount2", + Counts: &activity.CountsRecord{ + NonEntityClients: 2, + }, + }, + { + MountPath: "mount3", + Counts: &activity.CountsRecord{ + SecretSyncs: 2, + }, + }, + { + MountPath: "mount4", + Counts: &activity.CountsRecord{ + ACMEClients: 2, + }, + }, + }, + }, + }, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + { + MountPath: "mount2", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + { + MountPath: "mount3", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + { + MountPath: "mount4", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + }, + }, + }, + { + // 4 clients that have been seen before on ns1/mount1-4 + // 4 new clients on ns1/mount5-6 + name: "new clients new mounts", + currentMonthClients: []*activity.EntityRecord{ + {ClientID: "client_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns1", MountAccessor: "mount2"}, + {ClientID: "client_3", ClientType: secretSyncActivityType, NamespaceID: "ns1", MountAccessor: "mount3"}, + {ClientID: "client_4", ClientType: ACMEActivityType, NamespaceID: "ns1", MountAccessor: "mount4"}, + {ClientID: "new_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount5"}, + {ClientID: "new_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns1", MountAccessor: "mount6"}, + {ClientID: "new_3", ClientType: secretSyncActivityType, NamespaceID: "ns1", MountAccessor: "mount7"}, + {ClientID: "new_4", ClientType: ACMEActivityType, NamespaceID: "ns1", MountAccessor: "mount8"}, + }, + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 2, + SecretSyncs: 2, + NonEntityClients: 2, + ACMEClients: 2, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + { + MountPath: "mount2", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + { + MountPath: "mount3", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + { + MountPath: "mount4", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + { + MountPath: "mount5", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + { + MountPath: "mount6", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + { + MountPath: "mount7", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + { + MountPath: "mount8", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount5", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + { + MountPath: "mount6", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + { + MountPath: "mount7", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + { + MountPath: "mount8", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + }, + }, + }, + { + // 4 clients that have been seen before on ns1 + // 4 clients that are new on ns2-5 + name: "new clients new namespaces", + currentMonthClients: []*activity.EntityRecord{ + {ClientID: "client_1", ClientType: entityActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_3", ClientType: secretSyncActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "client_4", ClientType: ACMEActivityType, NamespaceID: "ns1", MountAccessor: "mount1"}, + {ClientID: "new_1", ClientType: entityActivityType, NamespaceID: "ns2", MountAccessor: "mount1"}, + {ClientID: "new_2", ClientType: nonEntityTokenActivityType, NamespaceID: "ns3", MountAccessor: "mount1"}, + {ClientID: "new_3", ClientType: secretSyncActivityType, NamespaceID: "ns4", MountAccessor: "mount1"}, + {ClientID: "new_4", ClientType: ACMEActivityType, NamespaceID: "ns5", MountAccessor: "mount1"}, + }, + wantMonth: &activity.MonthRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 2, + SecretSyncs: 2, + NonEntityClients: 2, + ACMEClients: 2, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns2", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns3", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns4", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + }, + }, + + { + NamespaceID: "ns5", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + NewClients: &activity.NewClientRecord{ + Counts: &activity.CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + Namespaces: []*activity.MonthlyNamespaceRecord{ + { + NamespaceID: "ns2", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + EntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns3", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + NonEntityClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns4", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + SecretSyncs: 1, + }, + }, + }, + }, + { + NamespaceID: "ns5", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + Mounts: []*activity.MountRecord{ + { + MountPath: "mount1", + Counts: &activity.CountsRecord{ + ACMEClients: 1, + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockHLL := mockHLLGetter(t) + core, _, _ := TestCoreUnsealed(t) + a := core.activityLog + month := newProcessMonth() + for _, c := range tc.currentMonthClients { + month.add(c) + } + endTime := timeutil.StartOfMonth(time.Now()) + startTime := timeutil.MonthsPreviousTo(3, endTime) + currentMonth := make(map[int64]*processMonth) + currentMonth[0] = month + monthRecord, err := a.computeCurrentMonthForBillingPeriodInternal(context.Background(), currentMonth, mockHLL, startTime, endTime) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + correctMountPaths := func(namespaces []*activity.MonthlyNamespaceRecord) { + for _, ns := range namespaces { + for _, mount := range ns.Mounts { + mount.MountPath = fmt.Sprintf(deletedMountFmt, mount.MountPath) + } + } + } + tc.wantMonth.Timestamp = timeutil.StartOfMonth(endTime).Unix() + correctMountPaths(tc.wantMonth.Namespaces) + correctMountPaths(tc.wantMonth.NewClients.Namespaces) + equalActivityMonthRecords(t, tc.wantMonth, monthRecord) + }) + } +} + // writeEntitySegment writes a single segment file with the given time and index for an entity func writeEntitySegment(t *testing.T, core *Core, ts time.Time, index int, item *activity.EntityActivityLog) { t.Helper() From 9af5c5c93364bb3fa7ea0f8a25351a53df49f4f8 Mon Sep 17 00:00:00 2001 From: miagilepner Date: Thu, 20 Jun 2024 15:23:52 +0200 Subject: [PATCH 10/24] add new clients to namespace counts (#27549) --- vault/activity/query.go | 4 +- vault/activity/query_test.go | 163 +++++++++++++++++++++++++++-------- 2 files changed, 129 insertions(+), 38 deletions(-) diff --git a/vault/activity/query.go b/vault/activity/query.go index e96935c4b137..8b9b773fd3cf 100644 --- a/vault/activity/query.go +++ b/vault/activity/query.go @@ -368,10 +368,10 @@ func (q *PrecomputedQuery) CombineWithCurrentMonth(currentMonth *MonthRecord) { existingNamespaceMounts[monthlyNamespaceRecord.NamespaceID] = monthlyNamespaceRecord } - // Get the counts of each mount per namespace in the current month, and increment + // Get the counts of new clients each mount per namespace in the current month, and increment // its total count in the precomputed query. These total values will be visible in the // by_namespace grouping in the final response data - for _, nsRecord := range currentMonth.Namespaces { + for _, nsRecord := range currentMonth.NewClients.Namespaces { namespaceId := nsRecord.NamespaceID // If the namespace already exists in the previous months, iterate through the mounts and increment the counts diff --git a/vault/activity/query_test.go b/vault/activity/query_test.go index d73c022911a7..f0043385ce60 100644 --- a/vault/activity/query_test.go +++ b/vault/activity/query_test.go @@ -388,28 +388,28 @@ func TestCombineWithCurrentMonth(t *testing.T) { { NamespaceID: "ns1", Counts: &CountsRecord{ - EntityClients: 2, - NonEntityClients: 2, - SecretSyncs: 2, - ACMEClients: 2, + EntityClients: 4, + NonEntityClients: 4, + SecretSyncs: 4, + ACMEClients: 4, }, Mounts: []*MountRecord{ { MountPath: "m1", Counts: &CountsRecord{ - EntityClients: 1, - NonEntityClients: 1, - SecretSyncs: 1, - ACMEClients: 1, + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, }, }, { MountPath: "m2", Counts: &CountsRecord{ - EntityClients: 1, - NonEntityClients: 1, - SecretSyncs: 1, - ACMEClients: 1, + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, }, }, }, @@ -417,28 +417,28 @@ func TestCombineWithCurrentMonth(t *testing.T) { { NamespaceID: "ns2", Counts: &CountsRecord{ - EntityClients: 2, - NonEntityClients: 2, - SecretSyncs: 2, - ACMEClients: 2, + EntityClients: 4, + NonEntityClients: 4, + SecretSyncs: 4, + ACMEClients: 4, }, Mounts: []*MountRecord{ { MountPath: "m1", Counts: &CountsRecord{ - EntityClients: 1, - NonEntityClients: 1, - SecretSyncs: 1, - ACMEClients: 1, + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, }, }, { MountPath: "m3", Counts: &CountsRecord{ - EntityClients: 1, - NonEntityClients: 1, - SecretSyncs: 1, - ACMEClients: 1, + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, }, }, }, @@ -446,28 +446,119 @@ func TestCombineWithCurrentMonth(t *testing.T) { { NamespaceID: "ns3", Counts: &CountsRecord{ - EntityClients: 2, - NonEntityClients: 2, - SecretSyncs: 2, - ACMEClients: 2, + EntityClients: 4, + NonEntityClients: 4, + SecretSyncs: 4, + ACMEClients: 4, }, Mounts: []*MountRecord{ { MountPath: "m1", Counts: &CountsRecord{ - EntityClients: 1, - NonEntityClients: 1, - SecretSyncs: 1, - ACMEClients: 1, + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, }, }, { MountPath: "m2", Counts: &CountsRecord{ - EntityClients: 1, - NonEntityClients: 1, - SecretSyncs: 1, - ACMEClients: 1, + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, + }, + }, + }, + }, + }, + NewClients: &NewClientRecord{ + Namespaces: []*MonthlyNamespaceRecord{ + { + NamespaceID: "ns1", + Counts: &CountsRecord{ + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, + }, + Mounts: []*MountRecord{ + { + MountPath: "m1", + Counts: &CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + }, + { + MountPath: "m2", + Counts: &CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns2", + Counts: &CountsRecord{ + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, + }, + Mounts: []*MountRecord{ + { + MountPath: "m1", + Counts: &CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + }, + { + MountPath: "m3", + Counts: &CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + }, + }, + }, + { + NamespaceID: "ns3", + Counts: &CountsRecord{ + EntityClients: 2, + NonEntityClients: 2, + SecretSyncs: 2, + ACMEClients: 2, + }, + Mounts: []*MountRecord{ + { + MountPath: "m1", + Counts: &CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, + }, + { + MountPath: "m2", + Counts: &CountsRecord{ + EntityClients: 1, + NonEntityClients: 1, + SecretSyncs: 1, + ACMEClients: 1, + }, }, }, }, From 25438b2238fe94e7bf4bbdc08b4447827534e6b1 Mon Sep 17 00:00:00 2001 From: John-Michael Faircloth Date: Thu, 20 Jun 2024 12:34:44 -0500 Subject: [PATCH 11/24] docs: clarify auth jwt upgrade guide versions (#27552) --- .../partials/known-issues/1_16-jwt_auth_bound_audiences.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx b/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx index 0dbd75ec9182..78fda89c074e 100644 --- a/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx +++ b/website/content/partials/known-issues/1_16-jwt_auth_bound_audiences.mdx @@ -10,7 +10,7 @@ #### Issue A behavior change was made in the jwt auth plugin to address CVE-2024-5798. Since the behavior change was a breaking change, we reverted the change in -the versions after 1.15.10 and 1.16.4. However, the behavior change will go +the versions 1.15.11 and 1.16.5 and later. However, the behavior change will go into effect in 1.17. The new behavior requires that the `bound_audiences` parameter of "jwt" roles From d23db14c460f247d70e918f429d74a291d66b44c Mon Sep 17 00:00:00 2001 From: Sarah Chavis <62406755+schavis@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:48:30 -0700 Subject: [PATCH 12/24] [DOCS: SPE-827] Add autopilot known issue to 1.15 docs and 1.16/1.17 release notes (#27454) * Update 1.15 docs with autopilot known issue * add autopilot issue to 1.16 and 1.17 release notes as known issue --- website/content/docs/release-notes/1.15.0.mdx | 2 +- website/content/docs/release-notes/1.16.1.mdx | 1 + website/content/docs/release-notes/1.17.0.mdx | 1 + .../docs/upgrading/upgrade-to-1.15.x.mdx | 2 ++ .../known-issues/1_15-auto-upgrade.mdx | 27 +++++++++++++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 website/content/partials/known-issues/1_15-auto-upgrade.mdx diff --git a/website/content/docs/release-notes/1.15.0.mdx b/website/content/docs/release-notes/1.15.0.mdx index 9397f647c380..6ff50af92f0c 100644 --- a/website/content/docs/release-notes/1.15.0.mdx +++ b/website/content/docs/release-notes/1.15.0.mdx @@ -30,7 +30,7 @@ description: |- | 1.15.0 - 1.15.5 | [Audit fails to recover from panics when formatting audit entries](/vault/docs/upgrading/upgrade-to-1.15.x#audit-fails-to-recover-from-panics-when-formatting-audit-entries) | | 1.15.0 - 1.15.7 | [Vault Enterprise performance standby nodes audit all request headers regardless of settings](/vault/docs/upgrading/upgrade-to-1.15.x#vault-enterprise-performance-standby-nodes-audit-all-request-headers) | | 1.15.3 - 1.15.9 | [New nodes added by autopilot upgrades provisioned with the wrong version](/vault/docs/upgrading/upgrade-to-1.15.x#new-nodes-added-by-autopilot-upgrades-provisioned-with-the-wrong-version) | - +| 1.15.8+ | [Autopilot upgrade for Vault Enterprise fails](/vault/docs/upgrading/upgrade-to-1.15.x#autopilot) ## Vault companion updates diff --git a/website/content/docs/release-notes/1.16.1.mdx b/website/content/docs/release-notes/1.16.1.mdx index 110b72ff7d74..ddf68c228d82 100644 --- a/website/content/docs/release-notes/1.16.1.mdx +++ b/website/content/docs/release-notes/1.16.1.mdx @@ -21,6 +21,7 @@ description: |- | 1.16.0+ | [LDAP auth entity alias names no longer include upndomain](/vault/docs/upgrading/upgrade-to-1.16.x#ldap-auth-entity-alias-names-no-longer-include-upndomain) | 1.16.0+ | [Azure secrets engine role creation failing](/vault/docs/upgrading/upgrade-to-1.16.x#azure-secrets-engine-role-creation-failing) | 1.16.1 - 1.16.3 | [New nodes added by autopilot upgrades provisioned with the wrong version](/vault/docs/upgrading/upgrade-to-1.15.x#new-nodes-added-by-autopilot-upgrades-provisioned-with-the-wrong-version) +| 1.15.8+ | [Autopilot upgrade for Vault Enterprise fails](/vault/docs/upgrading/upgrade-to-1.15.x#autopilot) ## Vault companion updates diff --git a/website/content/docs/release-notes/1.17.0.mdx b/website/content/docs/release-notes/1.17.0.mdx index a17b2e8874e1..6d17558f8e40 100644 --- a/website/content/docs/release-notes/1.17.0.mdx +++ b/website/content/docs/release-notes/1.17.0.mdx @@ -20,6 +20,7 @@ description: |- | Beta feature deprecated (1.17) | [Request limiter deprecated](/vault/docs/upgrading/upgrade-to-1.17.x#request-limiter) | | Known issue (1.17.0+) | [PKI OCSP GET requests can return HTTP redirect responses](/vault/docs/upgrading/upgrade-to-1.17.x#pki-ocsp) | | Known issue (1.17.0) | [Vault Agent and Vault Proxy consume excessive amounts of CPU](/vault/docs/upgrading/upgrade-to-1.17.x#agent-proxy-cpu-1-17) | +| Known issue (1.15.8+) | [Autopilot upgrade for Vault Enterprise fails](/vault/docs/upgrading/upgrade-to-1.15.x#autopilot) ## Vault companion updates diff --git a/website/content/docs/upgrading/upgrade-to-1.15.x.mdx b/website/content/docs/upgrading/upgrade-to-1.15.x.mdx index 903a3bd6ad6c..d08ed8a506b6 100644 --- a/website/content/docs/upgrading/upgrade-to-1.15.x.mdx +++ b/website/content/docs/upgrading/upgrade-to-1.15.x.mdx @@ -47,6 +47,8 @@ option. ## Known issues and workarounds +@include 'known-issues/1_15-auto-upgrade.mdx' + @include 'known-issues/transit-managed-keys-panics.mdx' @include 'known-issues/transit-managed-keys-sign-fails.mdx' diff --git a/website/content/partials/known-issues/1_15-auto-upgrade.mdx b/website/content/partials/known-issues/1_15-auto-upgrade.mdx new file mode 100644 index 000000000000..25d27e90d9cb --- /dev/null +++ b/website/content/partials/known-issues/1_15-auto-upgrade.mdx @@ -0,0 +1,27 @@ + + + +### Autopilot upgrade for Vault Enterprise fails + +#### Affected versions + +- 1.15.8+ + +#### Issue + +The expected process for voter status management does not occur during the +autopilot upgrade process. The autopilot upgrade process adds new nodes (new version) as +`target_version_non_voters` then `target_version_voters` but existing nodes (old +version) fail to upgrade and maintain their current voter status. + +#### Workaround + +1. Disable autopilot upgrades and manually upgrade all nodes in the cluster. +1. Explicitly set the `autopilot_upgrade_version` parameter to the desired + version in the raft `storage` stanza on your new nodes: + + ```hcl + storage "raft" { + autopilot_upgrade_version = "1.15.8" + } + ``` From d4da61fc4e987de07ca9b60604fddfb83314a3b2 Mon Sep 17 00:00:00 2001 From: davidadeleon <56207066+davidadeleon@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:46:01 -0400 Subject: [PATCH 13/24] CE side change for vault-24636 (#26675) --- vault/login_mfa.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/vault/login_mfa.go b/vault/login_mfa.go index abe3f70192f8..2a14b8110703 100644 --- a/vault/login_mfa.go +++ b/vault/login_mfa.go @@ -2653,6 +2653,31 @@ func (b *LoginMFABackend) MemDBMFALoginEnforcementConfigByNameAndNamespace(name, return eConfig.Clone() } +func (b *LoginMFABackend) MemDBMFALoginEnforcementConfigByID(id string) (*mfa.MFAEnforcementConfig, error) { + if id == "" { + return nil, fmt.Errorf("missing config id") + } + + txn := b.db.Txn(false) + defer txn.Abort() + + eConfigRaw, err := txn.First(memDBMFALoginEnforcementsTable, "id", id) + if err != nil { + return nil, fmt.Errorf("failed to fetch MFA login enforcement config from memdb using id: %w", err) + } + + if eConfigRaw == nil { + return nil, nil + } + + eConfig, ok := eConfigRaw.(*mfa.MFAEnforcementConfig) + if !ok { + return nil, fmt.Errorf("invalid type for MFA login enforcement config in memdb") + } + + return eConfig.Clone() +} + func (b *LoginMFABackend) MemDBMFALoginEnforcementConfigIterator() (memdb.ResultIterator, error) { txn := b.db.Txn(false) defer txn.Abort() @@ -2710,6 +2735,32 @@ func (b *LoginMFABackend) deleteMFALoginEnforcementConfigByNameAndNamespace(ctx return nil } +func (b *LoginMFABackend) MemDBDeleteMFALoginEnforcementConfigByID(id string) error { + if id == "" { + return nil + } + + txn := b.db.Txn(true) + defer txn.Abort() + + eConfig, err := b.MemDBMFALoginEnforcementConfigByID(id) + if err != nil { + return err + } + + if eConfig == nil { + return nil + } + + err = txn.Delete(memDBMFALoginEnforcementsTable, eConfig) + if err != nil { + return err + } + + txn.Commit() + return nil +} + func (b *LoginMFABackend) MemDBDeleteMFALoginEnforcementConfigByNameAndNamespace(name, namespaceId, tableName string) error { if name == "" || namespaceId == "" { return nil From 24826743123bd34977f896fc0d2c58cf66c564e5 Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:40:28 -0700 Subject: [PATCH 14/24] UI: Create starter Auth::Page component (#27478) * move OktaNumberChallenge and AuthForm to AuthPage component * return from didReceiveAttrs if component is being torn down * update auth form test * change passed task to an auth action * update auth form unit test * fix return * update jsdoc for auth form * add docs * add comments, last little cleanup, pass API error to okta number challenge * separate tests and move Auth::Page specific logic out of auth form integration test * fix test typos * fix page tests --- ui/app/components/auth-form.js | 103 +---- ui/app/components/auth/page.hbs | 23 ++ ui/app/components/auth/page.js | 108 +++++ ui/app/controllers/vault/cluster/auth.js | 4 - ui/app/templates/components/auth-form.hbs | 368 +++++++++--------- .../components/okta-number-challenge.hbs | 63 ++- ui/app/templates/vault/cluster/auth.hbs | 17 +- ui/tests/helpers/auth/auth-form-selectors.ts | 15 + ui/tests/helpers/general-selectors.ts | 1 + .../integration/components/auth-form-test.js | 292 +++++--------- .../integration/components/auth/page-test.js | 205 ++++++++++ .../components/okta-number-challenge-test.js | 49 +-- ui/tests/unit/components/auth-form-test.js | 33 +- 13 files changed, 709 insertions(+), 572 deletions(-) create mode 100644 ui/app/components/auth/page.hbs create mode 100644 ui/app/components/auth/page.js create mode 100644 ui/tests/helpers/auth/auth-form-selectors.ts create mode 100644 ui/tests/integration/components/auth/page-test.js diff --git a/ui/app/components/auth-form.js b/ui/app/components/auth-form.js index cf7e93102c95..d87161b29048 100644 --- a/ui/app/components/auth-form.js +++ b/ui/app/components/auth-form.js @@ -3,7 +3,6 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import Ember from 'ember'; import { next } from '@ember/runloop'; import { service } from '@ember/service'; import { match, or } from '@ember/object/computed'; @@ -11,27 +10,25 @@ import { dasherize } from '@ember/string'; import Component from '@ember/component'; import { computed } from '@ember/object'; import { allSupportedAuthBackends, supportedAuthBackends } from 'vault/helpers/supported-auth-backends'; -import { task, timeout } from 'ember-concurrency'; +import { task } from 'ember-concurrency'; import { waitFor } from '@ember/test-waiters'; import { v4 as uuidv4 } from 'uuid'; /** * @module AuthForm - * The `AuthForm` is used to sign users into Vault. + * The AuthForm displays the form used to sign users into Vault and passes input data to the Auth::Page component which handles authentication * - * @example ```js - * // All properties are passed in via query params. - * ``` + * @example + * * - * @param {string} wrappedToken - The auth method that is currently selected in the dropdown. - * @param {object} cluster - The auth method that is currently selected in the dropdown. This corresponds to an Ember Model. + * @param {string} wrappedToken - Token that can be used to login if added directly to the URL via the "wrapped_token" query param + * @param {object} cluster - The cluster model which contains information such as cluster id, name and boolean for if the cluster is in standby * @param {string} namespace- The currently active namespace. * @param {string} selectedAuth - The auth method that is currently selected in the dropdown. - * @param {function} onSuccess - Fired on auth success. - * @param {function} [setOktaNumberChallenge] - Sets whether we are waiting for okta number challenge to be used to sign in. - * @param {boolean} [waitingForOktaNumberChallenge=false] - Determines if we are waiting for the Okta Number Challenge to sign in. - * @param {function} [setCancellingAuth] - Sets whether we are cancelling or not the login authentication for Okta Number Challenge. - * @param {boolean} [cancelAuthForOktaNumberChallenge=false] - Determines if we are cancelling the login authentication for the Okta Number Challenge. + * @param {function} performAuth - Callback that triggers authenticate task in the parent, backend type (i.e. 'okta') and relevant auth data are passed as args + * @param {string} error - Error returned by the parent authenticate task, message is generated by the auth service handleError method + * @param {boolean} authIsRunning - Boolean that relays whether or not the authenticate task is running + * @param {boolean} delayIsIdle - Boolean that relays whether or not the delayAuthMessageReminder parent task is idle */ const DEFAULTS = { @@ -49,7 +46,7 @@ export default Component.extend(DEFAULTS, { csp: service('csp-event'), version: service(), - // passed in via a query param + // set by query params, passed from parent Auth::Page component selectedAuth: null, methods: null, cluster: null, @@ -58,9 +55,6 @@ export default Component.extend(DEFAULTS, { // internal oldNamespace: null, - // number answer for okta number challenge if applicable - oktaNumberChallengeAnswer: null, - authMethods: computed('version.isEnterprise', function () { return this.version.isEnterprise ? allSupportedAuthBackends() : supportedAuthBackends(); }), @@ -74,18 +68,13 @@ export default Component.extend(DEFAULTS, { namespace: ns, selectedAuth: newMethod, oldSelectedAuth: oldMethod, - cancelAuthForOktaNumberChallenge: cancelAuth, } = this; - // if we are cancelling the login then we reset the number challenge answer and cancel the current authenticate and polling tasks - if (cancelAuth) { - this.set('oktaNumberChallengeAnswer', null); - this.authenticate.cancelAll(); - this.pollForOktaNumberChallenge.cancelAll(); - } next(() => { if (!token && (oldNS === null || oldNS !== ns)) { this.fetchMethods.perform(); } + // don't set any variables if the component is being torn down + if (this.isDestroyed || this.isDestroying) return; this.set('oldNamespace', ns); // we only want to trigger this once if (token && !oldToken) { @@ -235,65 +224,7 @@ export default Component.extend(DEFAULTS, { }) ), - showLoading: or('isLoading', 'authenticate.isRunning', 'fetchMethods.isRunning', 'unwrapToken.isRunning'), - - authenticate: task( - waitFor(function* (backendType, data) { - const { - selectedAuth, - cluster: { id: clusterId }, - } = this; - try { - if (backendType === 'okta') { - this.pollForOktaNumberChallenge.perform(data.nonce, data.path); - } else { - this.delayAuthMessageReminder.perform(); - } - const authResponse = yield this.auth.authenticate({ - clusterId, - backend: backendType, - data, - selectedAuth, - }); - this.onSuccess(authResponse, backendType, data); - } catch (e) { - this.set('isLoading', false); - if (!this.auth.mfaError) { - this.set('error', `Authentication failed: ${this.auth.handleError(e)}`); - } - } - }) - ), - - pollForOktaNumberChallenge: task(function* (nonce, mount) { - // yield for 1s to wait to see if there is a login error before polling - yield timeout(1000); - if (this.error) { - return; - } - let response = null; - this.setOktaNumberChallenge(true); - this.setCancellingAuth(false); - // keep polling /auth/okta/verify/:nonce API every 1s until a response is given with the correct number for the Okta Number Challenge - while (response === null) { - // when testing, the polling loop causes promises to be rejected making acceptance tests fail - // so disable the poll in tests - if (Ember.testing) { - return; - } - yield timeout(1000); - response = yield this.auth.getOktaNumberChallengeAnswer(nonce, mount); - } - this.set('oktaNumberChallengeAnswer', response); - }), - - delayAuthMessageReminder: task(function* () { - if (Ember.testing) { - yield timeout(0); - } else { - yield timeout(5000); - } - }), + showLoading: or('isLoading', 'authIsRunning', 'fetchMethods.isRunning', 'unwrapToken.isRunning'), actions: { doSubmit(passedData, event, token) { @@ -326,7 +257,7 @@ export default Component.extend(DEFAULTS, { data.path = 'okta'; } } - return this.authenticate.unlinked().perform(backend.type, data); + return this.performAuth(backend.type, data); }, handleError(e) { this.setProperties({ @@ -334,9 +265,5 @@ export default Component.extend(DEFAULTS, { error: e ? this.auth.handleError(e) : null, }); }, - returnToLoginFromOktaNumberChallenge() { - this.setOktaNumberChallenge(false); - this.set('oktaNumberChallengeAnswer', null); - }, }, }); diff --git a/ui/app/components/auth/page.hbs b/ui/app/components/auth/page.hbs new file mode 100644 index 000000000000..e8b4d0c2e2e7 --- /dev/null +++ b/ui/app/components/auth/page.hbs @@ -0,0 +1,23 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +~}} + +{{#if this.waitingForOktaNumberChallenge}} + +{{else}} + +{{/if}} \ No newline at end of file diff --git a/ui/app/components/auth/page.js b/ui/app/components/auth/page.js new file mode 100644 index 000000000000..83fb397116e8 --- /dev/null +++ b/ui/app/components/auth/page.js @@ -0,0 +1,108 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import Ember from 'ember'; +import { service } from '@ember/service'; +import { task, timeout } from 'ember-concurrency'; +import { waitFor } from '@ember/test-waiters'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +/** + * @module AuthPage + * The Auth::Page wraps OktaNumberChallenge and AuthForm to manage the login flow and is responsible for calling the authenticate method + * + * @example + * + * + * @param {string} wrappedToken - Query param value of a wrapped token that can be used to login when added directly to the URL via the "wrapped_token" query param + * @param {object} cluster - The route model which is the ember data cluster model. contains information such as cluster id, name and boolean for if the cluster is in standby + * @param {string} namespace- Namespace query param, passed to AuthForm and set by typing in namespace input or URL + * @param {string} selectedAuth - The auth method selected in the dropdown, passed to auth service's authenticate method + * @param {function} onSuccess - Callback that fires the "onAuthResponse" action in the auth controller and handles transitioning after success + */ + +export default class AuthPageComponent extends Component { + @service auth; + + @tracked authError = null; + @tracked oktaNumberChallengeAnswer = ''; + @tracked waitingForOktaNumberChallenge = false; + + @action + performAuth(backendType, data) { + this.authenticate.unlinked().perform(backendType, data); + } + + @task + @waitFor + *delayAuthMessageReminder() { + if (Ember.testing) { + yield timeout(0); + } else { + yield timeout(5000); + } + } + + @task + @waitFor + *authenticate(backendType, data) { + const { + selectedAuth, + cluster: { id: clusterId }, + } = this.args; + try { + if (backendType === 'okta') { + this.pollForOktaNumberChallenge.perform(data.nonce, data.path); + } else { + this.delayAuthMessageReminder.perform(); + } + const authResponse = yield this.auth.authenticate({ + clusterId, + backend: backendType, + data, + selectedAuth, + }); + + this.args.onSuccess(authResponse, backendType, data); + } catch (e) { + if (!this.auth.mfaError) { + this.authError = `Authentication failed: ${this.auth.handleError(e)}`; + } + } + } + + @task + @waitFor + *pollForOktaNumberChallenge(nonce, mount) { + // yield for 1s to wait to see if there is a login error before polling + yield timeout(1000); + if (this.authError) return; + + this.waitingForOktaNumberChallenge = true; + // keep polling /auth/okta/verify/:nonce API every 1s until response returns with correct_number + let response = null; + while (response === null) { + // disable polling for tests otherwise promises reject and acceptance tests fail + if (Ember.testing) return; + + yield timeout(1000); + response = yield this.auth.getOktaNumberChallengeAnswer(nonce, mount); + } + // display correct number so user can select on personal MFA device + this.oktaNumberChallengeAnswer = response; + } + + @action + onCancel() { + // reset variables and stop polling tasks if canceling login + this.authError = null; + this.oktaNumberChallengeAnswer = null; + this.waitingForOktaNumberChallenge = false; + this.authenticate.cancelAll(); + this.pollForOktaNumberChallenge.cancelAll(); + } +} diff --git a/ui/app/controllers/vault/cluster/auth.js b/ui/app/controllers/vault/cluster/auth.js index 75c5cf52114f..66a798cac0cd 100644 --- a/ui/app/controllers/vault/cluster/auth.js +++ b/ui/app/controllers/vault/cluster/auth.js @@ -101,9 +101,5 @@ export default Controller.extend({ mfaErrors: null, }); }, - cancelAuthentication() { - this.set('cancelAuth', true); - this.set('waitingForOktaNumberChallenge', false); - }, }, }); diff --git a/ui/app/templates/components/auth-form.hbs b/ui/app/templates/components/auth-form.hbs index 66f135f2e940..f9fee4c71ff3 100644 --- a/ui/app/templates/components/auth-form.hbs +++ b/ui/app/templates/components/auth-form.hbs @@ -3,202 +3,192 @@ SPDX-License-Identifier: BUSL-1.1 ~}}
- {{#if (and this.waitingForOktaNumberChallenge (not this.cancelAuthForOktaNumberChallenge))}} - - {{else}} - {{#if this.hasMethodsWithPath}} - + + {{or method.id (capitalize method.type)}} + + + {{/let}} + {{/each}} +
  • + + Other + +
  • + + + {{/if}} +
    + + {{#if this.selectedAuthBackend.path}} +
    +

    {{this.selectedAuthBackend.path}}

    + + {{this.selectedAuthBackend.mountDescription}} + +
    {{/if}} -
    - - {{#if this.selectedAuthBackend.path}} -
    -

    {{this.selectedAuthBackend.path}}

    - - {{this.selectedAuthBackend.mountDescription}} - -
    - {{/if}} - {{#if (or (not this.hasMethodsWithPath) (not this.selectedAuthIsPath))}} - +
    +
    + {{else if (eq this.providerName "token")}} +
    + +
    + +
    +
    + {{else}} +
    + +
    + +
    +
    +
    + +
    + +
    +
    + {{/if}} + {{#if (not-eq this.selectedAuthBackend.type "token")}} - - {{else}} -
    - {{#if (eq this.providerName "github")}} -
    - -
    - -
    -
    - {{else if (eq this.providerName "token")}} -
    - -
    - -
    -
    - {{else}} -
    - -
    - -
    -
    -
    - -
    - -
    -
    - {{/if}} - {{#if (not-eq this.selectedAuthBackend.type "token")}} - - {{/if}} - + {{#if (and this.delayIsIdle this.showLoading)}} + - {{#if (and this.delayAuthMessageReminder.isIdle this.showLoading)}} - - {{/if}} - - {{/if}} -
    - {{/if}} + {{/if}} + + {{/if}} + \ No newline at end of file diff --git a/ui/app/templates/components/okta-number-challenge.hbs b/ui/app/templates/components/okta-number-challenge.hbs index 8f08fbc571ba..4064a7dc7cf1 100644 --- a/ui/app/templates/components/okta-number-challenge.hbs +++ b/ui/app/templates/components/okta-number-challenge.hbs @@ -2,42 +2,35 @@ Copyright (c) HashiCorp, Inc. SPDX-License-Identifier: BUSL-1.1 ~}} - +{{! todo move to auth/ folder? }}
    -
    -

    - To finish signing in, you will need to complete an additional MFA step.

    - {{#if @hasError}} -
    - - -
    - {{else if @correctAnswer}} -
    -

    Okta - verification

    -

    Select the following number to complete verification:

    -

    {{@correctAnswer}}

    -
    - {{else}} -
    -
    - -
    -

    Please wait...

    -
    -
    -
    - {{/if}} -
    +

    + To finish signing in, you will need to complete an additional MFA step. +

    + {{#if @hasError}} + + {{else if @correctAnswer}} +

    + Okta verification +

    +

    Select the following number to complete verification:

    +

    + {{@correctAnswer}} +

    + {{else}} +
    + +

    Please wait...

    +
    + {{/if}}
    +
    \ No newline at end of file diff --git a/ui/app/templates/vault/cluster/auth.hbs b/ui/app/templates/vault/cluster/auth.hbs index cb4027416878..db28f398c5ca 100644 --- a/ui/app/templates/vault/cluster/auth.hbs +++ b/ui/app/templates/vault/cluster/auth.hbs @@ -38,17 +38,9 @@ @color="tertiary" {{on "click" (fn (mut this.mfaAuthData) null)}} /> - {{else if this.waitingForOktaNumberChallenge}} - {{/if}}

    - {{if (or this.mfaAuthData this.waitingForOktaNumberChallenge) "Authenticate" "Sign in to Vault"}} + {{if this.mfaAuthData "Authenticate" "Sign in to Vault"}}

    {{/if}} @@ -101,17 +93,12 @@ @onError={{fn (mut this.mfaErrors)}} /> {{else}} - {{/if}} diff --git a/ui/tests/helpers/auth/auth-form-selectors.ts b/ui/tests/helpers/auth/auth-form-selectors.ts new file mode 100644 index 000000000000..a266fe86bdb0 --- /dev/null +++ b/ui/tests/helpers/auth/auth-form-selectors.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +export const AUTH_FORM = { + form: '[data-test-auth-form]', + login: '[data-test-auth-submit]', + tabs: (method: string) => (method ? `[data-test-auth-method="${method}"]` : '[data-test-auth-method]'), + description: '[data-test-description]', + roleInput: '[data-test-role]', + input: (item: string) => `[data-test-${item}]`, // i.e. role, token, password or username + mountPathInput: '[data-test-auth-form-mount-path]', + moreOptions: '[data-test-auth-form-options-toggle]', +}; diff --git a/ui/tests/helpers/general-selectors.ts b/ui/tests/helpers/general-selectors.ts index 9a0421492722..44294db7adbe 100644 --- a/ui/tests/helpers/general-selectors.ts +++ b/ui/tests/helpers/general-selectors.ts @@ -17,6 +17,7 @@ export const GENERAL = { secretTab: (name: string) => `[data-test-secret-list-tab="${name}"]`, flashMessage: '[data-test-flash-message]', latestFlashContent: '[data-test-flash-message]:last-of-type [data-test-flash-message-body]', + inlineAlert: '[data-test-inline-alert]', filter: (name: string) => `[data-test-filter="${name}"]`, filterInput: '[data-test-filter-input]', diff --git a/ui/tests/integration/components/auth-form-test.js b/ui/tests/integration/components/auth-form-test.js index 1cb4ef6b051a..07060597b3db 100644 --- a/ui/tests/integration/components/auth-form-test.js +++ b/ui/tests/integration/components/auth-form-test.js @@ -4,172 +4,120 @@ */ import { later, _cancelTimers as cancelTimers } from '@ember/runloop'; -import EmberObject from '@ember/object'; -import { resolve } from 'rsvp'; -import Service from '@ember/service'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render, settled } from '@ember/test-helpers'; +import { click, fillIn, render, settled } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import sinon from 'sinon'; -import { create } from 'ember-cli-page-object'; -import authForm from '../../pages/components/auth-form'; -import { validate } from 'uuid'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { Response } from 'miragejs'; - -const component = create(authForm); - -const workingAuthService = Service.extend({ - authenticate() { - return resolve({}); - }, - handleError() {}, - setLastFetch() {}, -}); - -const routerService = Service.extend({ - transitionTo() { - return { - followRedirects() { - return resolve(); - }, - }; - }, -}); +import { GENERAL } from 'vault/tests/helpers/general-selectors'; +import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors'; module('Integration | Component | auth form', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); hooks.beforeEach(function () { - this.owner.register('service:router', routerService); this.router = this.owner.lookup('service:router'); - this.onSuccess = sinon.spy(); + this.selectedAuth = 'token'; + this.performAuth = sinon.spy(); + this.renderComponent = async () => { + return render(hbs` + `); + }; }); - const CSP_ERR_TEXT = `Error This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI.`; - test('it renders error on CSP violation', async function (assert) { - assert.expect(2); - this.set('cluster', EmberObject.create({ standby: true })); - this.set('selectedAuth', 'token'); - await render(hbs``); - assert.false(component.errorMessagePresent, false); - this.owner.lookup('service:csp-event').handleEvent({ violatedDirective: 'connect-src' }); - await settled(); - assert.strictEqual(component.errorText, CSP_ERR_TEXT); + test('it calls performAuth on submit', async function (assert) { + await this.renderComponent(); + await fillIn(AUTH_FORM.input('token'), '123token'); + await click(AUTH_FORM.login); + const [type, data] = this.performAuth.lastCall.args; + assert.strictEqual(type, 'token', 'performAuth is called with type'); + assert.propEqual(data, { token: '123token' }, 'performAuth is called with data'); }); - test('it renders with vault style errors', async function (assert) { - assert.expect(1); - this.server.get('/auth/token/lookup-self', () => { - return new Response(400, { 'Content-Type': 'application/json' }, { errors: ['Not allowed'] }); - }); - - this.set('cluster', EmberObject.create({})); - this.set('selectedAuth', 'token'); - await render(hbs``); - await component.login(); - assert.strictEqual(component.errorText, 'Error Authentication failed: Not allowed'); + test('it disables sign in button when authIsRunning', async function (assert) { + this.authIsRunning = true; + await this.renderComponent(); + assert.dom(AUTH_FORM.login).isDisabled('sign in button is disabled'); + assert.dom(`${AUTH_FORM.login} [data-test-icon="loading"]`).exists('sign in button renders loading icon'); }); - test('it renders AdapterError style errors', async function (assert) { - assert.expect(1); - this.server.get('/auth/token/lookup-self', () => { - return new Response(400, { 'Content-Type': 'application/json' }, { errors: ['API Error here'] }); - }); - - this.set('cluster', EmberObject.create({})); - this.set('selectedAuth', 'token'); - await render(hbs``); - return component.login().then(() => { - assert.strictEqual( - component.errorText, - 'Error Authentication failed: API Error here', - 'shows the error from the API' + test('it renders alert info message when delayIsIdle', async function (assert) { + this.delayIsIdle = true; + this.authIsRunning = true; + await this.renderComponent(); + assert + .dom(GENERAL.inlineAlert) + .hasText( + 'If login takes longer than usual, you may need to check your device for an MFA notification, or contact your administrator if login times out.' ); - }); }); test('it renders no tabs when no methods are passed', async function (assert) { - const methods = { - 'approle/': { - type: 'approle', - }, - }; this.server.get('/sys/internal/ui/mounts', () => { - return { data: { auth: methods } }; + return { + data: { + auth: { + 'approle/': { + type: 'approle', + }, + }, + }, + }; }); - await render(hbs``); + await this.renderComponent(); - assert.strictEqual(component.tabs.length, 0, 'renders a tab for every backend'); - server.shutdown(); + assert.dom(AUTH_FORM.tabs()).doesNotExist(); }); test('it renders all the supported methods and Other tab when methods are present', async function (assert) { - const methods = { - 'foo/': { - type: 'userpass', - }, - 'approle/': { - type: 'approle', - }, - }; this.server.get('/sys/internal/ui/mounts', () => { - return { data: { auth: methods } }; + return { + data: { + auth: { + 'foo/': { + type: 'userpass', + }, + 'approle/': { + type: 'approle', + }, + }, + }, + }; }); - this.set('cluster', EmberObject.create({})); - await render(hbs``); - - assert.strictEqual(component.tabs.length, 2, 'renders a tab for userpass and Other'); - assert.strictEqual(component.tabs.objectAt(0).name, 'foo', 'uses the path in the label'); - assert.strictEqual(component.tabs.objectAt(1).name, 'Other', 'second tab is the Other tab'); - }); - test('it renders the description', async function (assert) { - const methods = { - 'approle/': { - type: 'userpass', - description: 'app description', - }, - }; - this.server.get('/sys/internal/ui/mounts', () => { - return { data: { auth: methods } }; - }); - this.set('cluster', EmberObject.create({})); - await render(hbs``); + await this.renderComponent(); - assert.strictEqual( - component.descriptionText, - 'app description', - 'renders a description for auth methods' - ); + assert.dom(AUTH_FORM.tabs()).exists({ count: 2 }); + assert.dom(AUTH_FORM.tabs('foo')).exists('tab uses the path in the label'); + assert.dom(AUTH_FORM.tabs('other')).exists('second tab is the Other tab'); }); - test('it calls authenticate with the correct path', async function (assert) { - this.owner.unregister('service:auth'); - this.owner.register('service:auth', workingAuthService); - this.auth = this.owner.lookup('service:auth'); - const authSpy = sinon.spy(this.auth, 'authenticate'); - const methods = { - 'foo/': { - type: 'userpass', - }, - }; + test('it renders the description', async function (assert) { + this.selectedAuth = null; this.server.get('/sys/internal/ui/mounts', () => { - return { data: { auth: methods } }; + return { + data: { + auth: { + 'approle/': { + type: 'userpass', + description: 'app description', + }, + }, + }, + }; }); - - this.set('cluster', EmberObject.create({})); - this.set('selectedAuth', 'foo/'); - await render(hbs``); - await component.login(); - - await settled(); - assert.ok(authSpy.calledOnce, 'a call to authenticate was made'); - const { data } = authSpy.getCall(0).args[0]; - assert.strictEqual(data.path, 'foo', 'uses the id for the path'); - authSpy.restore(); + await this.renderComponent(); + assert.dom(AUTH_FORM.description).hasText('app description'); }); test('it renders no tabs when no supported methods are present in passed methods', async function (assert) { @@ -181,45 +129,14 @@ module('Integration | Component | auth form', function (hooks) { this.server.get('/sys/internal/ui/mounts', () => { return { data: { auth: methods } }; }); - this.set('cluster', EmberObject.create({})); - await render(hbs``); + await this.renderComponent(); - server.shutdown(); - assert.strictEqual(component.tabs.length, 0, 'renders a tab for every backend'); - }); - - test('it makes a request to unwrap if passed a wrappedToken and logs in', async function (assert) { - assert.expect(3); - this.owner.register('service:auth', workingAuthService); - this.auth = this.owner.lookup('service:auth'); - const authSpy = sinon.stub(this.auth, 'authenticate'); - this.server.post('/sys/wrapping/unwrap', (_, req) => { - assert.strictEqual(req.url, '/v1/sys/wrapping/unwrap', 'makes call to unwrap the token'); - assert.strictEqual( - req.requestHeaders['X-Vault-Token'], - wrappedToken, - 'uses passed wrapped token for the unwrap' - ); - return { - auth: { - client_token: '12345', - }, - }; - }); - - const wrappedToken = '54321'; - this.set('wrappedToken', wrappedToken); - this.set('cluster', EmberObject.create({})); - await render( - hbs`` - ); - later(() => cancelTimers(), 50); - await settled(); - assert.ok(authSpy.calledOnce, 'a call to authenticate was made'); - authSpy.restore(); + assert.dom(AUTH_FORM.tabs()).doesNotExist(); }); test('it shows an error if unwrap errors', async function (assert) { + assert.expect(1); + this.wrappedToken = '54321'; this.server.post('/sys/wrapping/unwrap', () => { return new Response( 400, @@ -228,16 +145,11 @@ module('Integration | Component | auth form', function (hooks) { ); }); - this.set('wrappedToken', '54321'); - await render(hbs``); + await this.renderComponent(); later(() => cancelTimers(), 50); await settled(); - assert.strictEqual( - component.errorText, - 'Error Token unwrap failed: There was an error unwrapping!', - 'shows the error' - ); + assert.dom(GENERAL.messageError).hasText('Error Token unwrap failed: There was an error unwrapping!'); }); test('it should retain oidc role when mount path is changed', async function (assert) { @@ -269,36 +181,14 @@ module('Integration | Component | auth form', function (hooks) { }, }); - this.set('cluster', EmberObject.create({})); - await render(hbs``); - - await component.selectMethod('oidc'); - await component.oidcRole('foo'); - await component.oidcMoreOptions(); - await component.oidcMountPath('foo-oidc'); - assert.dom('[data-test-role]').hasValue('foo', 'role is retained when mount path is changed'); - await component.login(); - }); - - test('it should set nonce value as uuid for okta method type', async function (assert) { - assert.expect(1); - - this.server.post('/auth/okta/login/foo', (_, req) => { - const { nonce } = JSON.parse(req.requestBody); - assert.true(validate(nonce), 'Nonce value passed as uuid for okta login'); - return { - auth: { - client_token: '12345', - }, - }; - }); - - this.set('cluster', EmberObject.create({})); - await render(hbs``); + await this.renderComponent(); - await component.selectMethod('okta'); - await component.username('foo'); - await component.password('bar'); - await component.login(); + await fillIn(GENERAL.selectByAttr('auth-method'), 'oidc'); + await fillIn(AUTH_FORM.input('role'), 'foo'); + await click(AUTH_FORM.moreOptions); + await fillIn(AUTH_FORM.input('role'), 'foo'); + await fillIn(AUTH_FORM.mountPathInput, 'foo-oidc'); + assert.dom(AUTH_FORM.input('role')).hasValue('foo', 'role is retained when mount path is changed'); + await click(AUTH_FORM.login); }); }); diff --git a/ui/tests/integration/components/auth/page-test.js b/ui/tests/integration/components/auth/page-test.js new file mode 100644 index 000000000000..b9b55abd4ddf --- /dev/null +++ b/ui/tests/integration/components/auth/page-test.js @@ -0,0 +1,205 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { later, _cancelTimers as cancelTimers } from '@ember/runloop'; +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, fillIn, render, settled } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; +import sinon from 'sinon'; +import { validate } from 'uuid'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { Response } from 'miragejs'; +import { GENERAL } from 'vault/tests/helpers/general-selectors'; +import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors'; + +module('Integration | Component | auth | page ', function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + + hooks.beforeEach(function () { + this.router = this.owner.lookup('service:router'); + this.auth = this.owner.lookup('service:auth'); + this.cluster = { id: '1' }; + this.selectedAuth = 'token'; + this.onSuccess = sinon.spy(); + + this.renderComponent = async () => { + return render(hbs` + + `); + }; + }); + const CSP_ERR_TEXT = `Error This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI.`; + test('it renders error on CSP violation', async function (assert) { + assert.expect(2); + this.cluster.standby = true; + await this.renderComponent(); + assert.dom(GENERAL.messageError).doesNotExist(); + this.owner.lookup('service:csp-event').handleEvent({ violatedDirective: 'connect-src' }); + await settled(); + assert.dom(GENERAL.messageError).hasText(CSP_ERR_TEXT); + }); + + test('it renders with vault style errors', async function (assert) { + assert.expect(1); + this.server.get('/auth/token/lookup-self', () => { + return new Response(400, { 'Content-Type': 'application/json' }, { errors: ['Not allowed'] }); + }); + + await this.renderComponent(); + await click(AUTH_FORM.login); + assert.dom(GENERAL.messageError).hasText('Error Authentication failed: Not allowed'); + }); + + test('it renders AdapterError style errors', async function (assert) { + assert.expect(1); + this.server.get('/auth/token/lookup-self', () => { + return new Response(400, { 'Content-Type': 'application/json' }, { errors: ['API Error here'] }); + }); + + await this.renderComponent(); + await click(AUTH_FORM.login); + assert + .dom(GENERAL.messageError) + .hasText('Error Authentication failed: API Error here', 'shows the error from the API'); + }); + + test('it calls auth service authenticate method with expected args', async function (assert) { + assert.expect(1); + const authenticateStub = sinon.stub(this.auth, 'authenticate'); + this.selectedAuth = 'foo/'; // set to a non-default path + this.server.get('/sys/internal/ui/mounts', () => { + return { + data: { + auth: { + 'foo/': { + type: 'userpass', + }, + }, + }, + }; + }); + + await this.renderComponent(); + await fillIn(AUTH_FORM.input('username'), 'sandy'); + await fillIn(AUTH_FORM.input('password'), '1234'); + await click(AUTH_FORM.login); + const [actual] = authenticateStub.lastCall.args; + const expectedArgs = { + backend: 'userpass', + clusterId: '1', + data: { + username: 'sandy', + password: '1234', + path: 'foo', + }, + selectedAuth: 'foo/', + }; + assert.propEqual( + actual, + expectedArgs, + `it calls auth service authenticate method with expected args: ${JSON.stringify(actual)} ` + ); + }); + + test('it calls onSuccess with expected args', async function (assert) { + assert.expect(3); + this.server.get(`auth/token/lookup-self`, () => { + return { + data: { + policies: ['default'], + }, + }; + }); + + await this.renderComponent(); + await fillIn(AUTH_FORM.input('token'), 'mytoken'); + await click(AUTH_FORM.login); + const [authResponse, backendType, data] = this.onSuccess.lastCall.args; + const expected = { isRoot: false, namespace: '', token: 'vault-token☃1' }; + + assert.propEqual( + authResponse, + expected, + `it calls onSuccess with response: ${JSON.stringify(authResponse)} ` + ); + assert.strictEqual(backendType, 'token', `it calls onSuccess with backend type: ${backendType}`); + assert.propEqual(data, { token: 'mytoken' }, `it calls onSuccess with data: ${JSON.stringify(data)}`); + }); + + test('it makes a request to unwrap if passed a wrappedToken and logs in', async function (assert) { + assert.expect(3); + const authenticateStub = sinon.stub(this.auth, 'authenticate'); + this.wrappedToken = '54321'; + + this.server.post('/sys/wrapping/unwrap', (_, req) => { + assert.strictEqual(req.url, '/v1/sys/wrapping/unwrap', 'makes call to unwrap the token'); + assert.strictEqual( + req.requestHeaders['X-Vault-Token'], + this.wrappedToken, + 'uses passed wrapped token for the unwrap' + ); + return { + auth: { + client_token: '12345', + }, + }; + }); + + await this.renderComponent(); + later(() => cancelTimers(), 50); + await settled(); + const [actual] = authenticateStub.lastCall.args; + assert.propEqual( + actual, + { + backend: 'token', + clusterId: '1', + data: { + token: '12345', + }, + selectedAuth: 'token', + }, + `it calls auth service authenticate method with correct args: ${JSON.stringify(actual)} ` + ); + }); + + test('it should set nonce value as uuid for okta method type', async function (assert) { + assert.expect(4); + this.server.post('/auth/okta/login/foo', (_, req) => { + const { nonce } = JSON.parse(req.requestBody); + assert.true(validate(nonce), 'Nonce value passed as uuid for okta login'); + return { + auth: { + client_token: '12345', + policies: ['default'], + }, + }; + }); + + await this.renderComponent(); + + await fillIn(GENERAL.selectByAttr('auth-method'), 'okta'); + await fillIn(AUTH_FORM.input('username'), 'foo'); + await fillIn(AUTH_FORM.input('password'), 'bar'); + await click(AUTH_FORM.login); + assert + .dom('[data-test-okta-number-challenge]') + .hasText( + 'To finish signing in, you will need to complete an additional MFA step. Please wait... Back to login', + 'renders okta number challenge on submit' + ); + await click('[data-test-back-button]'); + assert.dom(AUTH_FORM.form).exists('renders auth form on return to login'); + assert.dom(GENERAL.selectByAttr('auth-method')).hasValue('okta', 'preserves method type on back'); + }); +}); diff --git a/ui/tests/integration/components/okta-number-challenge-test.js b/ui/tests/integration/components/okta-number-challenge-test.js index d1871dd5b5b4..2c57009258e1 100644 --- a/ui/tests/integration/components/okta-number-challenge-test.js +++ b/ui/tests/integration/components/okta-number-challenge-test.js @@ -7,32 +7,40 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render, click } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; +import sinon from 'sinon'; -module('Integration | Component | okta-number-challenge', function (hooks) { +module('Integration | Component | auth | okta-number-challenge', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { this.oktaNumberChallengeAnswer = null; this.hasError = false; + this.onCancel = sinon.spy(); + this.renderComponent = async () => { + return render(hbs` + + `); + }; }); test('it should render correct descriptions', async function (assert) { - await render(hbs``); - + await this.renderComponent(); assert .dom('[data-test-okta-number-challenge-description]') .includesText( 'To finish signing in, you will need to complete an additional MFA step.', 'Correct description renders' ); - assert - .dom('[data-test-okta-number-challenge-loading]') - .includesText('Please wait...', 'Correct loading description renders'); + assert.dom('[data-test-loading]').includesText('Please wait...', 'Correct loading description renders'); }); test('it should show correct number for okta number challenge', async function (assert) { - this.set('oktaNumberChallengeAnswer', 1); - await render(hbs``); + this.oktaNumberChallengeAnswer = 1; + await this.renderComponent(); assert .dom('[data-test-okta-number-challenge-description]') .includesText( @@ -40,35 +48,30 @@ module('Integration | Component | okta-number-challenge', function (hooks) { 'Correct description renders' ); assert - .dom('[data-test-okta-number-challenge-verification-type]') + .dom('[data-test-verification-type]') .includesText('Okta verification', 'Correct verification type renders'); assert - .dom('[data-test-okta-number-challenge-verification-description]') + .dom('[data-test-description]') .includesText( 'Select the following number to complete verification:', 'Correct verification description renders' ); - assert - .dom('[data-test-okta-number-challenge-answer]') - .includesText('1', 'Correct okta number challenge answer renders'); + assert.dom('[data-test-answer]').includesText('1', 'Correct okta number challenge answer renders'); }); test('it should show error screen', async function (assert) { - this.set('hasError', true); - await render( - hbs`` - ); + this.hasError = 'Authentication failed: multi-factor authentication denied'; + await this.renderComponent(); + assert .dom('[data-test-okta-number-challenge-description]') - .includesText( + .hasTextContaining( 'To finish signing in, you will need to complete an additional MFA step.', 'Correct description renders' ); - assert - .dom('[data-test-message-error]') - .includesText('There was a problem', 'Displays error that there was a problem'); - await click('[data-test-return-from-okta-number-challenge]'); - assert.true(this.returnToLogin, 'onReturnToLogin was triggered'); + assert.dom('[data-test-message-error]').hasText(`Error ${this.hasError}`); + await click('[data-test-back-button]'); + assert.true(this.onCancel.calledOnce, 'onCancel is called'); }); }); diff --git a/ui/tests/unit/components/auth-form-test.js b/ui/tests/unit/components/auth-form-test.js index 81113101aa35..a054a6e2cb23 100644 --- a/ui/tests/unit/components/auth-form-test.js +++ b/ui/tests/unit/components/auth-form-test.js @@ -16,24 +16,23 @@ module('Unit | Component | auth-form', function (hooks) { const component = this.owner.lookup('component:auth-form'); component.reopen({ methods: [], // eslint-disable-line + // performAuth is a callback passed from the parent component + // that is called in the return of the doSubmit method + // this component is not glimmerized and testing this functionality + // in an integration test requires additional role setup so + // stubbing here to test it is called with the correct args // eslint-disable-next-line - authenticate: { - unlinked() { - return { - perform(type, data) { - assert.deepEqual( - type, - 'token', - `Token type correctly passed to authenticate method for ${component.providerName}` - ); - assert.deepEqual( - data, - { token: component.token }, - `Token passed to authenticate method for ${component.providerName}` - ); - }, - }; - }, + performAuth(type, data) { + assert.deepEqual( + type, + 'token', + `Token type correctly passed to authenticate method for ${component.providerName}` + ); + assert.deepEqual( + data, + { token: component.token }, + `Token passed to authenticate method for ${component.providerName}` + ); }, }); From 5f078e2d39fe3555068bae1973a4d1aabcfa921d Mon Sep 17 00:00:00 2001 From: Robert <17119716+robmonte@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:05:12 -0500 Subject: [PATCH 15/24] Add chroot known-issue and sync activation-flag release note (#27558) * Add chroot known-issue and activation-flag release note * Fix reference link --- website/content/docs/release-notes/1.16.1.mdx | 1 + .../content/docs/upgrading/upgrade-to-1.16.x.mdx | 9 +++++++++ .../1_16_secrets-sync-chroot-activation.mdx | 15 +++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 website/content/partials/known-issues/1_16_secrets-sync-chroot-activation.mdx diff --git a/website/content/docs/release-notes/1.16.1.mdx b/website/content/docs/release-notes/1.16.1.mdx index ddf68c228d82..b3600afb0c32 100644 --- a/website/content/docs/release-notes/1.16.1.mdx +++ b/website/content/docs/release-notes/1.16.1.mdx @@ -19,6 +19,7 @@ description: |- | 1.16.0+ | [Default LCQ enabled when upgrading pre-1.9](/vault/docs/upgrading/upgrade-to-1.16.x#default-lcq-pre-1.9-upgrade) | | 1.16.0+ | [External plugin environment variables take precedence over server variables](/vault/docs/upgrading/upgrade-to-1.16.x#external-plugin-variables) | 1.16.0+ | [LDAP auth entity alias names no longer include upndomain](/vault/docs/upgrading/upgrade-to-1.16.x#ldap-auth-entity-alias-names-no-longer-include-upndomain) +| 1.16.0+ | [Secrets Sync now requires a one-time flag to operate](/vault/docs/upgrading/upgrade-to-1.16.x#secrets-sync-now-requires-setting-a-one-time-flag-before-use) | 1.16.0+ | [Azure secrets engine role creation failing](/vault/docs/upgrading/upgrade-to-1.16.x#azure-secrets-engine-role-creation-failing) | 1.16.1 - 1.16.3 | [New nodes added by autopilot upgrades provisioned with the wrong version](/vault/docs/upgrading/upgrade-to-1.15.x#new-nodes-added-by-autopilot-upgrades-provisioned-with-the-wrong-version) | 1.15.8+ | [Autopilot upgrade for Vault Enterprise fails](/vault/docs/upgrading/upgrade-to-1.15.x#autopilot) diff --git a/website/content/docs/upgrading/upgrade-to-1.16.x.mdx b/website/content/docs/upgrading/upgrade-to-1.16.x.mdx index 08d8ed5d4192..ae7a3bead879 100644 --- a/website/content/docs/upgrading/upgrade-to-1.16.x.mdx +++ b/website/content/docs/upgrading/upgrade-to-1.16.x.mdx @@ -81,6 +81,13 @@ userattr="userprincipalname" Refer to the [LDAP auth method (API)](/vault/api-docs/auth/ldap) page for more details on the configuration. +### Secrets Sync now requires setting a one-time flag before use + +To use the Secrets Sync feature, the feature must be activated with a new one-time +operation called an activation-flag. The feature is gated until a Vault operator +decides to trigger the flag. More information can be found in the +[secrets sync documentation](/vault/docs/sync#activating-the-feature). + ## Known issues and workarounds @include 'known-issues/1_16-jwt_auth_bound_audiences.mdx' @@ -104,3 +111,5 @@ more details on the configuration. @include 'known-issues/1_13-reload-census-panic-standby.mdx' @include 'known-issues/autopilot-upgrade-upgrade-version.mdx' + +@include 'known-issues/1_16_secrets-sync-chroot-activation.mdx' diff --git a/website/content/partials/known-issues/1_16_secrets-sync-chroot-activation.mdx b/website/content/partials/known-issues/1_16_secrets-sync-chroot-activation.mdx new file mode 100644 index 000000000000..ab322a4c1c38 --- /dev/null +++ b/website/content/partials/known-issues/1_16_secrets-sync-chroot-activation.mdx @@ -0,0 +1,15 @@ +### Secrets Sync cannot be activated from chroot namespace + +#### Affected versions + +- 1.16.0+ + +#### Issue + +Secrets Sync cannot be activated from the chroot namespace. The Secrets Sync feature +now requires a new activation-flag to be enabled before it can be used. Writing to +any `sys/activation-flags/` path currently requires root namespace access. + +#### Workaround +Users can request a Vault operator to activate the feature from the root namespace +if they lack the necessary access. From 6f00ce45d20f3f9cf6e2d8ebefac3dfbda88b4cc Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Fri, 21 Jun 2024 11:06:53 -0700 Subject: [PATCH 16/24] UI: remove `current_billing_period` from dashboard activity log request (#27559) * remove current_billing_period from dashboard request * add changelog * remove timestamp from assertion * update mirage --- changelog/27559.txt | 3 + ui/app/adapters/clients/activity.js | 9 ++ ui/app/components/clients/no-data.hbs | 10 +- .../dashboard/client-count-card.hbs | 16 ++-- .../components/dashboard/client-count-card.js | 9 +- ui/app/models/clients/config.js | 2 + ui/mirage/handlers/clients.js | 6 +- ui/tests/acceptance/dashboard-test.js | 2 +- .../components/clients/no-data-test.js | 93 +++++++++++++++++++ .../dashboard/client-count-card-test.js | 55 +++++++---- 10 files changed, 169 insertions(+), 36 deletions(-) create mode 100644 changelog/27559.txt create mode 100644 ui/tests/integration/components/clients/no-data-test.js diff --git a/changelog/27559.txt b/changelog/27559.txt new file mode 100644 index 000000000000..a9afccdc98d5 --- /dev/null +++ b/changelog/27559.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Remove deprecated `current_billing_period` from dashboard activity log request +``` diff --git a/ui/app/adapters/clients/activity.js b/ui/app/adapters/clients/activity.js index 67c4a7478695..3e9455228e94 100644 --- a/ui/app/adapters/clients/activity.js +++ b/ui/app/adapters/clients/activity.js @@ -5,6 +5,7 @@ import ApplicationAdapter from '../application'; import { formatDateObject } from 'core/utils/client-count-utils'; +import { debug } from '@ember/debug'; export default class ActivityAdapter extends ApplicationAdapter { // javascript localizes new Date() objects but all activity log data is stored in UTC @@ -33,4 +34,12 @@ export default class ActivityAdapter extends ApplicationAdapter { }); } } + + urlForFindRecord(id) { + // debug reminder so model is stored in Ember data with the same id for consistency + if (id !== 'clients/activity') { + debug(`findRecord('clients/activity') should pass 'clients/activity' as the id, you passed: '${id}'`); + } + return `${this.buildURL()}/internal/counters/activity`; + } } diff --git a/ui/app/components/clients/no-data.hbs b/ui/app/components/clients/no-data.hbs index b6fa080dcd8d..113c1e33c733 100644 --- a/ui/app/components/clients/no-data.hbs +++ b/ui/app/components/clients/no-data.hbs @@ -3,13 +3,13 @@ SPDX-License-Identifier: BUSL-1.1 ~}} -{{#if (eq @config.enabled "On")}} +{{#if (or @config.reportingEnabled (eq @config.enabled "On"))}} -{{else}} +{{else if @config}} {{/if}} +{{else}} + {{/if}} \ No newline at end of file diff --git a/ui/app/components/dashboard/client-count-card.hbs b/ui/app/components/dashboard/client-count-card.hbs index 2b9b4fa32026..b904566483ef 100644 --- a/ui/app/components/dashboard/client-count-card.hbs +++ b/ui/app/components/dashboard/client-count-card.hbs @@ -14,10 +14,10 @@
    - {{#if this.hasActivity}} - {{#if this.fetchClientActivity.isRunning}} - - {{else}} + {{#if this.fetchClientActivity.isRunning}} + + {{else}} + {{#if this.activityData}}
    @@ -34,15 +34,13 @@ {{on "click" (perform this.fetchClientActivity)}} data-test-refresh /> - + Updated {{date-format this.updatedAt "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
    + {{else}} + {{/if}} - {{else}} - {{! This will likely never show since the clients activity api has changed to always return data. In the past it - would return no activity data. Adding this empty state here to match the current client count behavior }} - {{/if}} \ No newline at end of file diff --git a/ui/app/components/dashboard/client-count-card.js b/ui/app/components/dashboard/client-count-card.js index 51c2d174ddc4..6b613bfe53ad 100644 --- a/ui/app/components/dashboard/client-count-card.js +++ b/ui/app/components/dashboard/client-count-card.js @@ -23,7 +23,7 @@ export default class DashboardClientCountCard extends Component { @service store; @tracked activityData = null; - @tracked hasActivity = false; + @tracked activityConfig = null; @tracked updatedAt = null; constructor() { @@ -53,11 +53,10 @@ export default class DashboardClientCountCard extends Component { this.updatedAt = timestamp.now().toISOString(); try { - this.activityData = yield this.store.queryRecord('clients/activity', { - current_billing_period: true, - }); - this.hasActivity = this.activityData.id === 'no-data' ? false : true; + this.activityData = yield this.store.findRecord('clients/activity', 'clients/activity'); } catch (error) { + // used for rendering the "No data" empty state, swallow any errors requesting config data + this.activityConfig = yield this.store.queryRecord('clients/config', {}).catch(() => null); this.error = error; } } diff --git a/ui/app/models/clients/config.js b/ui/app/models/clients/config.js index 69288e23fb76..26c1663a9740 100644 --- a/ui/app/models/clients/config.js +++ b/ui/app/models/clients/config.js @@ -35,8 +35,10 @@ export default class ClientsConfigModel extends Model { @attr('number') minimumRetentionMonths; + // refers specifically to the activitylog and will always be on for enterprise @attr('string') enabled; + // reporting_enabled is for automated reporting and only true of the customer hasn’t opted-out of automated license reporting @attr('boolean') reportingEnabled; @attr('date') billingStartTimestamp; diff --git a/ui/mirage/handlers/clients.js b/ui/mirage/handlers/clients.js index a8e08e81cba2..1ac7659833f1 100644 --- a/ui/mirage/handlers/clients.js +++ b/ui/mirage/handlers/clients.js @@ -215,9 +215,9 @@ export default function (server) { server.get('/sys/internal/counters/activity', (schema, req) => { let { start_time, end_time } = req.queryParams; - if (req.queryParams.current_billing_period) { - // { current_billing_period: true } automatically queries the activity log - // from the builtin license start timestamp to the current month + if (!start_time && !end_time) { + // if there are no date query params, the activity log default behavior + // queries from the builtin license start timestamp to the current month start_time = LICENSE_START.toISOString(); end_time = STATIC_NOW.toISOString(); } diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js index aec0a89ec7f4..d1119d4df25f 100644 --- a/ui/tests/acceptance/dashboard-test.js +++ b/ui/tests/acceptance/dashboard-test.js @@ -402,7 +402,7 @@ module('Acceptance | landing page dashboard', function (hooks) { assert.true(version.isEnterprise, 'version is enterprise'); assert.strictEqual(currentURL(), '/vault/dashboard'); assert.dom(DASHBOARD.cardName('client-count')).exists(); - const response = await this.store.peekRecord('clients/activity', 'some-activity-id'); + const response = await this.store.findRecord('clients/activity', 'clients/activity'); assert.dom('[data-test-client-count-title]').hasText('Client count'); assert.dom('[data-test-stat-text="Total"] .stat-label').hasText('Total'); assert.dom('[data-test-stat-text="Total"] .stat-value').hasText(formatNumber([response.total.clients])); diff --git a/ui/tests/integration/components/clients/no-data-test.js b/ui/tests/integration/components/clients/no-data-test.js new file mode 100644 index 000000000000..7605d24d6a85 --- /dev/null +++ b/ui/tests/integration/components/clients/no-data-test.js @@ -0,0 +1,93 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'vault/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { GENERAL } from 'vault/tests/helpers/general-selectors'; +import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; + +module('Integration | Component | clients/no-data', function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + + hooks.beforeEach(async function () { + this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub()); + this.store = this.owner.lookup('service:store'); + this.setConfig = async (data) => { + // the clients/config model does some funky serializing for the "enabled" param + // so stubbing the request here instead of just the model for additional coverage + this.server.get('sys/internal/counters/config', () => { + return { + request_id: '25a94b99-b49a-c4ac-cb7b-5ba0eb390a25', + data, + }; + }); + return this.store.queryRecord('clients/config', {}); + }; + this.renderComponent = async () => { + return render(hbs``); + }; + }); + + test('it renders empty state when enabled is "on"', async function (assert) { + assert.expect(2); + const data = { + enabled: 'default-enabled', + reporting_enabled: false, + }; + ``; + this.config = await this.setConfig(data); + await this.renderComponent(); + assert.dom(GENERAL.emptyStateTitle).hasText('No data received'); + assert + .dom(GENERAL.emptyStateMessage) + .hasText('Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes.'); + }); + + test('it renders empty state when reporting_enabled is true', async function (assert) { + assert.expect(2); + const data = { + enabled: 'default-disabled', + reporting_enabled: true, + }; + this.config = await this.setConfig(data); + await this.renderComponent(); + assert.dom(GENERAL.emptyStateTitle).hasText('No data received'); + assert + .dom(GENERAL.emptyStateMessage) + .hasText('Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes.'); + }); + + test('it renders empty state when reporting is fully disabled', async function (assert) { + assert.expect(2); + const data = { + enabled: 'default-disabled', + reporting_enabled: false, + }; + this.config = await this.setConfig(data); + await this.renderComponent(); + assert.dom(GENERAL.emptyStateTitle).hasText('Data tracking is disabled'); + assert + .dom(GENERAL.emptyStateMessage) + .hasText( + 'Tracking is disabled, and no data is being collected. To turn it on, edit the configuration.' + ); + }); + + test('it renders empty state when config data is not available', async function (assert) { + assert.expect(2); + this.config = null; + await this.renderComponent(); + assert.dom(GENERAL.emptyStateTitle).hasText('Activity configuration data is unavailable'); + assert + .dom(GENERAL.emptyStateMessage) + .hasText( + 'Reporting status is unknown and could be enabled or disabled. Check the Vault logs for more information.' + ); + }); +}); diff --git a/ui/tests/integration/components/dashboard/client-count-card-test.js b/ui/tests/integration/components/dashboard/client-count-card-test.js index 388de5097e20..1542018a2904 100644 --- a/ui/tests/integration/components/dashboard/client-count-card-test.js +++ b/ui/tests/integration/components/dashboard/client-count-card-test.js @@ -8,6 +8,7 @@ import { setupRenderingTest } from 'vault/tests/helpers'; import { render, click } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; +import { Response } from 'miragejs'; import sinon from 'sinon'; import { STATIC_NOW } from 'vault/mirage/handlers/clients'; import timestamp from 'core/utils/timestamp'; @@ -21,21 +22,14 @@ module('Integration | Component | dashboard/client-count-card', function (hooks) setupMirage(hooks); test('it should display client count information', async function (assert) { - sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW)); - assert.expect(5); + assert.expect(6); + sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW)); // 1/25/24 const { months, total } = ACTIVITY_RESPONSE_STUB; const [latestMonth] = months.slice(-1); - this.server.get('sys/internal/counters/activity', (schema, req) => { + this.server.get('sys/internal/counters/activity', () => { // this assertion should be hit twice, once initially and then again clicking 'refresh' - assert.propEqual( - req.queryParams, - { current_billing_period: 'true' }, - 'it makes request to sys/internal/counters/activity with builtin license start time' - ); - return { - request_id: 'some-activity-id', - data: ACTIVITY_RESPONSE_STUB, - }; + assert.true(true, 'makes request to sys/internal/counters/activity'); + return { data: ACTIVITY_RESPONSE_STUB }; }); await render(hbs``); @@ -54,6 +48,7 @@ module('Integration | Component | dashboard/client-count-card', function (hooks) latestMonth.new_clients.counts.clients, ])}` ); + assert.dom('[data-test-updated-timestamp]').hasTextContaining('Updated Jan 25 2024'); // fires second request to /activity await click('[data-test-refresh]'); @@ -65,7 +60,6 @@ module('Integration | Component | dashboard/client-count-card', function (hooks) // stubbing this unrealistic response just to test component subtext logic this.server.get('sys/internal/counters/activity', () => { return { - request_id: 'some-activity-id', data: { by_namespace: [], months: [], total: {} }, }; }); @@ -75,19 +69,48 @@ module('Integration | Component | dashboard/client-count-card', function (hooks) assert.dom(CLIENT_COUNT.statText('New')).hasText('New No new client data available. -'); }); - test('it shows empty state if no activity data', async function (assert) { + test('it shows empty state if no activity data and reporting is enabled', async function (assert) { // the activity response has changed and now should ALWAYS return something // but adding this test until we update the adapter to reflect that - assert.expect(3); + assert.expect(4); this.server.get('sys/internal/counters/activity', () => { assert.true(true, 'makes request to sys/internal/counters/activity'); return { data: {} }; }); - + this.server.get('sys/internal/counters/config', () => { + assert.true(true, 'makes request to sys/internal/counters/config'); + return { + request_id: '25a94b99-b49a-c4ac-cb7b-5ba0eb390a25', + data: { reporting_enabled: true }, + }; + }); await render(hbs``); assert.dom(GENERAL.emptyStateTitle).hasText('No data received'); assert .dom(GENERAL.emptyStateMessage) .hasText('Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes.'); }); + + test('it shows empty state if no activity data and config data is unavailable', async function (assert) { + assert.expect(4); + this.server.get('sys/internal/counters/activity', () => { + assert.true(true, 'makes request to sys/internal/counters/activity'); + return { data: {} }; + }); + this.server.get('sys/internal/counters/config', () => { + assert.true(true, 'makes request to sys/internal/counters/config'); + return new Response( + 403, + { 'Content-Type': 'application/json' }, + JSON.stringify({ errors: ['permission denied'] }) + ); + }); + await render(hbs``); + assert.dom(GENERAL.emptyStateTitle).hasText('Activity configuration data is unavailable'); + assert + .dom(GENERAL.emptyStateMessage) + .hasText( + 'Reporting status is unknown and could be enabled or disabled. Check the Vault logs for more information.' + ); + }); }); From 7187b6f2c39e466539ee176508247b04ddd1e63f Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Fri, 21 Jun 2024 11:45:24 -0700 Subject: [PATCH 17/24] update package ent command and docfy readme (#27565) --- ui/docs/how-to-docfy.md | 7 +++++-- ui/package.json | 1 + ui/scripts/docfy-md.js | 2 ++ ui/scripts/generate-docs.sh | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ui/docs/how-to-docfy.md b/ui/docs/how-to-docfy.md index 644cfed706b6..50871acf8cc3 100644 --- a/ui/docs/how-to-docfy.md +++ b/ui/docs/how-to-docfy.md @@ -13,7 +13,9 @@ Side nav links correspond to the file + directory structure within the `docs/` d ## generating component docs -The `docs/components` directory is where _generated_ markdown files for components live after running `yarn docs`. **Do not edit component markdown files directly**. Instead, update markdown by making changes to the `jsdoc` and then re-running the generate command. The `docs/components/*` files are included in `.gitignore` so they are not pushed to main. `jsdoc-to-markdown` errors log in the console. +The `docs/components` directory is where _generated_ component markdown files are located after running `yarn docs`. **Do not edit component markdown files directly**. Instead, update markdown by making changes to the `jsdoc` and then re-running the generate command. The `docs/components/*` files are included in `.gitignore` so they are not pushed to main. `jsdoc-to-markdown` errors log in the console. + +> _If you have never run the `yarn docs` command before, you need to create the `docs/components` directory locally before running `yarn docfy-md` so the markdown has a place to go. `mkdir docs/components`_ ``` yarn docfy-md @@ -24,9 +26,10 @@ yarn docfy-md | Command | Description | | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `yarn docs` | generate markdown file for every\* component in the `addon/core` directory | -| `yarn docfy-md some-component-name` | generate markdown file for specific component | +| `yarn docfy-md some-component-name` | generate markdown file for **specific** component | | `yarn docfy-md read-more core` | generate markdown for `read-more` component in the `core` addon | | `yarn docfy-md pki-test null ./lib/pki/addon/components/pki-key-usage.ts` | optional third arg is the full component filepath (first arg will become markdown file name) | +| `mkdir docs/components` | create directory where the generated component markdown files will go | | `rm -f ./docs/components/*` | cleanup and delete generated component markdown files | > _\*replication and `shamir/*` components are skipped as these are not reused and should eventually be moved outside the `addon/core` directory_ diff --git a/ui/package.json b/ui/package.json index 35c1619db525..0377d0a7b692 100644 --- a/ui/package.json +++ b/ui/package.json @@ -32,6 +32,7 @@ "test": "concurrently --kill-others-on-fail -P -c \"auto\" -n lint:js,lint:hbs,vault \"yarn:lint:js:quiet\" \"yarn:lint:hbs:quiet\" \"node scripts/start-vault.js {@}\" --", "test:enos": "concurrently --kill-others-on-fail -P -c \"auto\" -n lint:js,lint:hbs,enos \"yarn:lint:js:quiet\" \"yarn:lint:hbs:quiet\" \"node scripts/enos-test-ember.js {@}\" --", "test:oss": "yarn run test -f='!enterprise' --split=8 --preserve-test-name --parallel", + "test:ent": "node scripts/start-vault.js -f='enterprise'", "test:quick": "node scripts/start-vault.js --split=8 --preserve-test-name --parallel", "test:quick-oss": "node scripts/start-vault.js -f='!enterprise' --split=8 --preserve-test-name --parallel", "test:filter": "node scripts/start-vault.js --server -f='!enterprise'", diff --git a/ui/scripts/docfy-md.js b/ui/scripts/docfy-md.js index a3f126cd6c8f..35758f6a1142 100644 --- a/ui/scripts/docfy-md.js +++ b/ui/scripts/docfy-md.js @@ -13,6 +13,8 @@ yarn docfy-md some-component or if the docs are for a component in an in-repo-addon or an engine: yarn docfy-md some-component name-of-engine + +see the readme ui/docs/how-to-docfy.md for more info */ const fs = require('fs'); diff --git a/ui/scripts/generate-docs.sh b/ui/scripts/generate-docs.sh index d1be67c3dfb3..268a9ad9e0ee 100644 --- a/ui/scripts/generate-docs.sh +++ b/ui/scripts/generate-docs.sh @@ -2,6 +2,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 +echo "Create components/ directory" +mkdir docs/components/ + echo "Generating markdown files for components in core addon..." # iterate over every .ts and .js file in core/addon/components (including nested files) From 45682dc090d36d323a3b63b0caa78e25bf328e86 Mon Sep 17 00:00:00 2001 From: davidadeleon <56207066+davidadeleon@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:09:07 -0400 Subject: [PATCH 18/24] VAULT-28281: Pass in accountName variable into validation function (#27563) * pass in correct accountName variable into validation function * modify test fixture to better test validation functionality * pass in accountName variable into error message * changelog --- changelog/27563.txt | 3 +++ physical/azure/azure.go | 4 ++-- physical/azure/azure_test.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 changelog/27563.txt diff --git a/changelog/27563.txt b/changelog/27563.txt new file mode 100644 index 000000000000..294b5e7cf642 --- /dev/null +++ b/changelog/27563.txt @@ -0,0 +1,3 @@ +```release-note:bug +storage/azure: Fix invalid account name initialization bug +``` \ No newline at end of file diff --git a/physical/azure/azure.go b/physical/azure/azure.go index b655df8d3c6d..fc016e7fe93c 100644 --- a/physical/azure/azure.go +++ b/physical/azure/azure.go @@ -68,8 +68,8 @@ func NewAzureBackend(conf map[string]string, logger log.Logger) (physical.Backen return nil, fmt.Errorf("'accountName' must be set") } } - if err := validateAccountName(name); err != nil { - return nil, fmt.Errorf("invalid account name %s: %w", name, err) + if err := validateAccountName(accountName); err != nil { + return nil, fmt.Errorf("invalid account name %s: %w", accountName, err) } accountKey := os.Getenv("AZURE_ACCOUNT_KEY") diff --git a/physical/azure/azure_test.go b/physical/azure/azure_test.go index 22344a6bca2e..2e491bd9b886 100644 --- a/physical/azure/azure_test.go +++ b/physical/azure/azure_test.go @@ -35,7 +35,7 @@ func testFixture(t *testing.T) (*AzureBackend, func()) { t.Helper() ts := time.Now().UnixNano() - name := fmt.Sprintf("vlt%d", ts) + name := fmt.Sprintf("vlt-%d", ts) _ = os.Setenv("AZURE_BLOB_CONTAINER", name) cleanup := func() {} From 89e9e0f2cd46431ed212e7f63ec0ea2e23a639e7 Mon Sep 17 00:00:00 2001 From: Ryan Cragun Date: Fri, 21 Jun 2024 15:24:46 -0600 Subject: [PATCH 19/24] VAULT-28307 enos: allow arm64 fips1402 and hsm editions (#27571) In preperation for arm64 builds of hsm, fips1402, and hsm.fips1402 editions of Vault Enterprise we'll allow them in our test scenarios. Signed-off-by: Ryan Cragun --- enos/enos-scenario-agent.hcl | 10 ++-------- enos/enos-scenario-autopilot.hcl | 7 ++++--- enos/enos-scenario-proxy.hcl | 10 ++-------- enos/enos-scenario-replication.hcl | 10 ++-------- enos/enos-scenario-seal-ha.hcl | 12 ++---------- enos/enos-scenario-smoke.hcl | 10 ++-------- enos/enos-scenario-upgrade.hcl | 7 ++++--- 7 files changed, 18 insertions(+), 48 deletions(-) diff --git a/enos/enos-scenario-agent.hcl b/enos/enos-scenario-agent.hcl index 9d609e3289ae..7f39df4823b9 100644 --- a/enos/enos-scenario-agent.hcl +++ b/enos/enos-scenario-agent.hcl @@ -36,12 +36,6 @@ scenario "agent" { artifact_type = ["package"] } - # HSM and FIPS 140-2 are only supported on amd64 - exclude { - arch = ["arm64"] - edition = ["ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] - } - # PKCS#11 can only be used on ent.hsm and ent.hsm.fips1402. exclude { seal = ["pkcs11"] @@ -54,8 +48,8 @@ scenario "agent" { arch = ["arm64"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. + # softhsm packages not available for leap/sles. Enos support for softhsm on amzn2 is + # not implemented yet. exclude { seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] diff --git a/enos/enos-scenario-autopilot.hcl b/enos/enos-scenario-autopilot.hcl index 35d353196626..902a2bedad5f 100644 --- a/enos/enos-scenario-autopilot.hcl +++ b/enos/enos-scenario-autopilot.hcl @@ -41,7 +41,8 @@ scenario "autopilot" { artifact_type = ["package"] } - # HSM and FIPS 140-2 are only supported on amd64 + # There are no published versions of these artifacts yet. We'll update this to exclude older + # versions after our initial publication of these editions for arm64. exclude { arch = ["arm64"] edition = ["ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] @@ -59,8 +60,8 @@ scenario "autopilot" { arch = ["arm64"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. + # softhsm packages not available for leap/sles. Enos support for softhsm on amzn2 is + # not implemented yet. exclude { seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] diff --git a/enos/enos-scenario-proxy.hcl b/enos/enos-scenario-proxy.hcl index 5e363d55379e..825226e3cdbf 100644 --- a/enos/enos-scenario-proxy.hcl +++ b/enos/enos-scenario-proxy.hcl @@ -36,12 +36,6 @@ scenario "proxy" { artifact_type = ["package"] } - # HSM and FIPS 140-2 are only supported on amd64 - exclude { - arch = ["arm64"] - edition = ["ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] - } - # PKCS#11 can only be used on ent.hsm and ent.hsm.fips1402. exclude { seal = ["pkcs11"] @@ -54,8 +48,8 @@ scenario "proxy" { arch = ["arm64"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. + # softhsm packages not available for leap/sles. Enos support for softhsm on amzn2 is + # not implemented yet. exclude { seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] diff --git a/enos/enos-scenario-replication.hcl b/enos/enos-scenario-replication.hcl index 79063e955d0b..7e700bb8ae17 100644 --- a/enos/enos-scenario-replication.hcl +++ b/enos/enos-scenario-replication.hcl @@ -43,12 +43,6 @@ scenario "replication" { artifact_type = ["package"] } - # HSM and FIPS 140-2 are only supported on amd64 - exclude { - arch = ["arm64"] - edition = ["ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] - } - # PKCS#11 can only be used on ent.hsm and ent.hsm.fips1402. exclude { primary_seal = ["pkcs11"] @@ -66,8 +60,8 @@ scenario "replication" { arch = ["arm64"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. + # softhsm packages not available for leap/sles. Enos support for softhsm on amzn2 is + # not implemented yet. exclude { primary_seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] diff --git a/enos/enos-scenario-seal-ha.hcl b/enos/enos-scenario-seal-ha.hcl index cf1c51836759..76de63d760ff 100644 --- a/enos/enos-scenario-seal-ha.hcl +++ b/enos/enos-scenario-seal-ha.hcl @@ -41,12 +41,6 @@ scenario "seal_ha" { artifact_type = ["package"] } - # HSM and FIPS 140-2 are only supported on amd64 - exclude { - arch = ["arm64"] - edition = ["ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] - } - # PKCS#11 can only be used on ent.hsm and ent.hsm.fips1402. exclude { primary_seal = ["pkcs11"] @@ -64,15 +58,13 @@ scenario "seal_ha" { arch = ["arm64"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. + # softhsm packages not available for leap/sles. Enos support for softhsm on amzn2 is + # not implemented yet. exclude { primary_seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. exclude { secondary_seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] diff --git a/enos/enos-scenario-smoke.hcl b/enos/enos-scenario-smoke.hcl index c98e02941c5c..edbe123ea455 100644 --- a/enos/enos-scenario-smoke.hcl +++ b/enos/enos-scenario-smoke.hcl @@ -35,12 +35,6 @@ scenario "smoke" { artifact_type = ["package"] } - # HSM and FIPS 140-2 are only supported on amd64 - exclude { - arch = ["arm64"] - edition = ["ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] - } - # PKCS#11 can only be used on ent.hsm and ent.hsm.fips1402. exclude { seal = ["pkcs11"] @@ -53,8 +47,8 @@ scenario "smoke" { arch = ["arm64"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. + # softhsm packages not available for leap/sles. Enos support for softhsm on amzn2 is + # not implemented yet. exclude { seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] diff --git a/enos/enos-scenario-upgrade.hcl b/enos/enos-scenario-upgrade.hcl index 1b3c10f89947..b15b38159b41 100644 --- a/enos/enos-scenario-upgrade.hcl +++ b/enos/enos-scenario-upgrade.hcl @@ -52,7 +52,8 @@ scenario "upgrade" { initial_version = [for e in matrix.initial_version : e if semverconstraint(e, "<1.11.0-0")] } - # HSM and FIPS 140-2 are only supported on amd64 + # There are no published versions of these artifacts yet. We'll update this to exclude older + # versions after our initial publication of these editions for arm64. exclude { arch = ["arm64"] edition = ["ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] @@ -70,8 +71,8 @@ scenario "upgrade" { arch = ["arm64"] } - # softhsm packages not available for leap/sles; Enos support for softhsm - # on amzn2 to be added later. + # softhsm packages not available for leap/sles. Enos support for softhsm on amzn2 is + # not implemented yet. exclude { seal = ["pkcs11"] distro = ["amzn2", "leap", "sles"] From 4e02a7a6731d5ab0bdc9bac4b282abca01505de9 Mon Sep 17 00:00:00 2001 From: Noelle Daley Date: Fri, 21 Jun 2024 16:49:54 -0700 Subject: [PATCH 20/24] UI: fix flaky form-related integration tests (#27537) * tests: await settled after calling cancelTimers to fix flakiness * chore: don't use assert.ok * tests: fix flaky mfa-test --- ui/app/components/auth-jwt.js | 5 +++-- ui/app/components/mfa/mfa-form.js | 8 +++++-- ui/app/templates/components/mfa/mfa-form.hbs | 8 +++---- ui/tests/acceptance/oidc-auth-method-test.js | 3 +++ .../integration/components/auth-form-test.js | 1 - .../integration/components/auth-jwt-test.js | 12 ++++++++-- .../components/control-group-success-test.js | 22 +++++++++++-------- .../components/edit-form-kmip-role-test.js | 18 +++++++-------- .../integration/components/edit-form-test.js | 14 ++++++------ .../integration/components/mfa-form-test.js | 20 ++++++++++++----- .../components/mount-backend-form-test.js | 8 +++---- ui/tests/unit/components/auth-jwt-test.js | 18 ++++++++++++--- 12 files changed, 89 insertions(+), 48 deletions(-) diff --git a/ui/app/components/auth-jwt.js b/ui/app/components/auth-jwt.js index a373ee86cfb0..610b6d09933b 100644 --- a/ui/app/components/auth-jwt.js +++ b/ui/app/components/auth-jwt.js @@ -2,14 +2,13 @@ * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ - +import Ember from 'ember'; import { service } from '@ember/service'; // ARG NOTE: Once you remove outer-html after glimmerizing you can remove the outer-html component import Component from './outer-html'; import { task, timeout, waitForEvent } from 'ember-concurrency'; import { debounce } from '@ember/runloop'; -const WAIT_TIME = 500; const ERROR_WINDOW_CLOSED = 'The provider window was closed before authentication was complete. Your web browser may have blocked or closed a pop-up window. Please check your settings and click Sign In to try again.'; const ERROR_MISSING_PARAMS = @@ -109,6 +108,8 @@ export default Component.extend({ watchPopup: task(function* (oidcWindow) { while (true) { + const WAIT_TIME = Ember.testing ? 50 : 500; + yield timeout(WAIT_TIME); if (!oidcWindow || oidcWindow.closed) { return this.handleOIDCError(ERROR_WINDOW_CLOSED); diff --git a/ui/app/components/mfa/mfa-form.js b/ui/app/components/mfa/mfa-form.js index 20b09ac4f638..484d13905f9a 100644 --- a/ui/app/components/mfa/mfa-form.js +++ b/ui/app/components/mfa/mfa-form.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: BUSL-1.1 */ +import Ember from 'ember'; import Component from '@glimmer/component'; import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; @@ -29,7 +30,7 @@ export const TOTP_VALIDATION_ERROR = export default class MfaForm extends Component { @service auth; - @tracked countdown; + @tracked countdown = 0; @tracked error; @tracked codeDelayMessage; @@ -104,7 +105,10 @@ export default class MfaForm extends Component { @task *newCodeDelay(message) { // parse validity period from error string to initialize countdown this.countdown = parseInt(message.match(/(\d\w seconds)/)[0].split(' ')[0]); - while (this.countdown) { + + if (Ember.testing) return; + + while (this.countdown > 0) { yield timeout(1000); this.countdown--; } diff --git a/ui/app/templates/components/mfa/mfa-form.hbs b/ui/app/templates/components/mfa/mfa-form.hbs index f5025b645780..c1f1b1d9119f 100644 --- a/ui/app/templates/components/mfa/mfa-form.hbs +++ b/ui/app/templates/components/mfa/mfa-form.hbs @@ -43,7 +43,7 @@ placeholder={{if (gt constraint.methods.length 1) "Enter passcode"}} spellcheck="false" autofocus="true" - disabled={{or this.validate.isRunning this.newCodeDelay.isRunning}} + disabled={{or this.validate.isRunning this.countdown}} @value={{constraint.passcode}} data-test-mfa-passcode={{index}} /> @@ -56,7 +56,7 @@ {{/if}} {{/each}} - {{#if this.newCodeDelay.isRunning}} + {{#if this.countdown}}
    @@ -66,10 +66,10 @@ @icon={{if this.validate.isRunning "loading"}} id="validate" type="submit" - disabled={{or this.validate.isRunning this.newCodeDelay.isRunning}} + disabled={{or this.validate.isRunning this.countdown}} data-test-mfa-validate /> - {{#if this.newCodeDelay.isRunning}} + {{#if this.countdown}} {{this.countdown}} {{/if}} diff --git a/ui/tests/acceptance/oidc-auth-method-test.js b/ui/tests/acceptance/oidc-auth-method-test.js index 82fb72a5e8a5..0dcaaffd2fe5 100644 --- a/ui/tests/acceptance/oidc-auth-method-test.js +++ b/ui/tests/acceptance/oidc-auth-method-test.js @@ -70,6 +70,7 @@ module('Acceptance | oidc auth method', function (hooks) { window.postMessage(buildMessage().data, window.origin); cancelTimers(); }, 100); + await click('[data-test-auth-submit]'); }); @@ -98,6 +99,7 @@ module('Acceptance | oidc auth method', function (hooks) { window.postMessage(buildMessage().data, window.origin); cancelTimers(); }, 50); + await click('[data-test-auth-submit]'); }); @@ -109,6 +111,7 @@ module('Acceptance | oidc auth method', function (hooks) { window.postMessage(buildMessage().data, window.origin); cancelTimers(); }, 50); + await click('[data-test-auth-submit]'); await waitUntil(() => find('[data-test-user-menu-trigger]')); await click('[data-test-user-menu-trigger]'); diff --git a/ui/tests/integration/components/auth-form-test.js b/ui/tests/integration/components/auth-form-test.js index 07060597b3db..c82ce4acf15d 100644 --- a/ui/tests/integration/components/auth-form-test.js +++ b/ui/tests/integration/components/auth-form-test.js @@ -147,7 +147,6 @@ module('Integration | Component | auth form', function (hooks) { await this.renderComponent(); later(() => cancelTimers(), 50); - await settled(); assert.dom(GENERAL.messageError).hasText('Error Token unwrap failed: There was an error unwrapping!'); }); diff --git a/ui/tests/integration/components/auth-jwt-test.js b/ui/tests/integration/components/auth-jwt-test.js index eb6c26cdb60d..1512129ef33a 100644 --- a/ui/tests/integration/components/auth-jwt-test.js +++ b/ui/tests/integration/components/auth-jwt-test.js @@ -166,7 +166,10 @@ module('Integration | Component | auth jwt', function (hooks) { await waitUntil(() => { return this.openSpy.calledOnce; }); + cancelTimers(); + await settled(); + const call = this.openSpy.getCall(0); assert.deepEqual( call.args, @@ -201,6 +204,8 @@ module('Integration | Component | auth jwt', function (hooks) { buildMessage({ data: { source: 'oidc-callback', state: 'state', foo: 'bar' } }) ); cancelTimers(); + await settled(); + assert.strictEqual(this.error, ERROR_MISSING_PARAMS, 'calls onError with params missing error'); }); @@ -226,9 +231,11 @@ module('Integration | Component | auth jwt', function (hooks) { return this.openSpy.calledOnce; }); this.window.trigger('message', buildMessage({ origin: 'http://hackerz.com' })); + cancelTimers(); await settled(); - assert.notOk(this.handler.called, 'should not call the submit handler'); + + assert.false(this.handler.called, 'should not call the submit handler'); }); test('oidc: fails silently when event is not trusted', async function (assert) { @@ -242,7 +249,8 @@ module('Integration | Component | auth jwt', function (hooks) { this.window.trigger('message', buildMessage({ isTrusted: false })); cancelTimers(); await settled(); - assert.notOk(this.handler.called, 'should not call the submit handler'); + + assert.false(this.handler.called, 'should not call the submit handler'); }); test('oidc: it should trigger error callback when role is not found', async function (assert) { diff --git a/ui/tests/integration/components/control-group-success-test.js b/ui/tests/integration/components/control-group-success-test.js index bda51e70bfa2..64ccf0ccc90b 100644 --- a/ui/tests/integration/components/control-group-success-test.js +++ b/ui/tests/integration/components/control-group-success-test.js @@ -71,25 +71,29 @@ module('Integration | Component | control group success', function (hooks) { this.set('model', MODEL); this.set('response', response); await render(hbs``); - assert.ok(component.showsNavigateMessage, 'shows unwrap message'); + + assert.true(component.showsNavigateMessage, 'shows unwrap message'); + await component.navigate(); later(() => cancelTimers(), 50); - return settled().then(() => { - assert.ok(this.controlGroup.markTokenForUnwrap.calledOnce, 'marks token for unwrap'); - assert.ok(this.router.transitionTo.calledOnce, 'calls router transition'); - }); + await settled(); + + assert.true(this.controlGroup.markTokenForUnwrap.calledOnce, 'marks token for unwrap'); + assert.true(this.router.transitionTo.calledOnce, 'calls router transition'); }); test('render without token', async function (assert) { assert.expect(2); this.set('model', MODEL); await render(hbs``); - assert.ok(component.showsUnwrapForm, 'shows unwrap form'); + + assert.true(component.showsUnwrapForm, 'shows unwrap form'); + await component.token('token'); component.unwrap(); later(() => cancelTimers(), 50); - return settled().then(() => { - assert.ok(component.showsJsonViewer, 'shows unwrapped data'); - }); + await settled(); + + assert.true(component.showsJsonViewer, 'shows unwrapped data'); }); }); diff --git a/ui/tests/integration/components/edit-form-kmip-role-test.js b/ui/tests/integration/components/edit-form-kmip-role-test.js index a949ca2e8c38..dac56709a450 100644 --- a/ui/tests/integration/components/edit-form-kmip-role-test.js +++ b/ui/tests/integration/components/edit-form-kmip-role-test.js @@ -224,15 +224,15 @@ module('Integration | Component | edit form kmip role', function (hooks) { click('[data-test-edit-form-submit]'); later(() => cancelTimers(), 50); - return settled().then(() => { - for (const afterStateKey of Object.keys(stateAfterSave)) { - assert.strictEqual( - model.get(afterStateKey), - stateAfterSave[afterStateKey], - `sets ${afterStateKey} on save` - ); - } - }); + await settled(); + + for (const afterStateKey of Object.keys(stateAfterSave)) { + assert.strictEqual( + model.get(afterStateKey), + stateAfterSave[afterStateKey], + `sets ${afterStateKey} on save` + ); + } }); } }); diff --git a/ui/tests/integration/components/edit-form-test.js b/ui/tests/integration/components/edit-form-test.js index ee4f9ebf726d..1c8a1a0596ee 100644 --- a/ui/tests/integration/components/edit-form-test.js +++ b/ui/tests/integration/components/edit-form-test.js @@ -68,12 +68,12 @@ module('Integration | Component | edit form', function (hooks) { component.submit(); later(() => cancelTimers(), 50); - return settled().then(() => { - assert.ok(saveSpy.calledOnce, 'calls passed onSave'); - assert.strictEqual(saveSpy.getCall(0).args[0].saveType, 'save'); - assert.deepEqual(saveSpy.getCall(0).args[0].model, this.model, 'passes model to onSave'); - const flash = this.owner.lookup('service:flash-messages'); - assert.strictEqual(flash.success.callCount, 1, 'calls flash message success'); - }); + await settled(); + + assert.true(saveSpy.calledOnce, 'calls passed onSave'); + assert.strictEqual(saveSpy.getCall(0).args[0].saveType, 'save'); + assert.deepEqual(saveSpy.getCall(0).args[0].model, this.model, 'passes model to onSave'); + const flash = this.owner.lookup('service:flash-messages'); + assert.strictEqual(flash.success.callCount, 1, 'calls flash message success'); }); }); diff --git a/ui/tests/integration/components/mfa-form-test.js b/ui/tests/integration/components/mfa-form-test.js index c5c65ed6960e..128b5b38519d 100644 --- a/ui/tests/integration/components/mfa-form-test.js +++ b/ui/tests/integration/components/mfa-form-test.js @@ -5,10 +5,9 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; +import { render, settled, fillIn, click, waitUntil, waitFor } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; -import { fillIn, click, waitUntil } from '@ember/test-helpers'; import { _cancelTimers as cancelTimers, later } from '@ember/runloop'; import { TOTP_VALIDATION_ERROR } from 'vault/components/mfa/mfa-form'; @@ -84,6 +83,12 @@ module('Integration | Component | mfa-form', function (hooks) { ); }); + test('it should render a submit button', async function (assert) { + await render(hbs``); + + assert.dom('[data-test-mfa-validate]').isNotDisabled('Button is not disabled by default'); + }); + test('it should render method selects and passcode inputs', async function (assert) { assert.expect(2); const duoConstraint = this.server.create('mfa-method', { type: 'duo', uses_passcode: true }); @@ -170,7 +175,6 @@ module('Integration | Component | mfa-form', function (hooks) { await click('[data-test-mfa-validate]'); }); - // TODO JLR: It doesn't appear that cancelTimers is working and tests wait for the full countdown test('it should show countdown on passcode already used and rate limit errors', async function (assert) { const messages = { used: 'code already used; new code is available in 45 seconds', @@ -184,12 +188,16 @@ module('Integration | Component | mfa-form', function (hooks) { throw { errors: [messages[code]] }; }, }); + const expectedTime = code === 'used' ? 45 : 15; + await render(hbs``); await fillIn('[data-test-mfa-passcode]', code); - later(() => cancelTimers(), 50); + await click('[data-test-mfa-validate]'); - const expectedTime = code === 'used' ? '45' : '15'; + + await waitFor('[data-test-mfa-countdown]'); + assert .dom('[data-test-mfa-countdown]') .includesText(expectedTime, 'countdown renders with correct initial value from error response'); @@ -209,6 +217,8 @@ module('Integration | Component | mfa-form', function (hooks) { await fillIn('[data-test-mfa-passcode]', 'test-code'); later(() => cancelTimers(), 50); + await settled(); + await click('[data-test-mfa-validate]'); assert .dom('[data-test-message-error]') diff --git a/ui/tests/integration/components/mount-backend-form-test.js b/ui/tests/integration/components/mount-backend-form-test.js index f1acedc1fe22..b824faa51b12 100644 --- a/ui/tests/integration/components/mount-backend-form-test.js +++ b/ui/tests/integration/components/mount-backend-form-test.js @@ -116,8 +116,8 @@ module('Integration | Component | mount backend form', function (hooks) { later(() => cancelTimers(), 50); await settled(); - assert.ok(spy.calledOnce, 'calls the passed success method'); - assert.ok( + assert.true(spy.calledOnce, 'calls the passed success method'); + assert.true( this.flashSuccessSpy.calledWith('Successfully mounted the approle auth method at foo.'), 'Renders correct flash message' ); @@ -184,8 +184,8 @@ module('Integration | Component | mount backend form', function (hooks) { later(() => cancelTimers(), 50); await settled(); - assert.ok(spy.calledOnce, 'calls the passed success method'); - assert.ok( + assert.true(spy.calledOnce, 'calls the passed success method'); + assert.true( this.flashSuccessSpy.calledWith('Successfully mounted the ssh secrets engine at foo.'), 'Renders correct flash message' ); diff --git a/ui/tests/unit/components/auth-jwt-test.js b/ui/tests/unit/components/auth-jwt-test.js index d4c3b0d06677..41ba7e3d6548 100644 --- a/ui/tests/unit/components/auth-jwt-test.js +++ b/ui/tests/unit/components/auth-jwt-test.js @@ -5,6 +5,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import { settled } from '@ember/test-helpers'; import EmberObject from '@ember/object'; import Evented from '@ember/object/evented'; import sinon from 'sinon'; @@ -29,9 +30,14 @@ module('Unit | Component | auth-jwt', function (hooks) { this.component.prepareForOIDC.perform(mockWindow.create()); this.component.window.trigger('message', { origin: 'http://anotherdomain.com', isTrusted: true }); - assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message'); + assert.true( + this.errorSpy.notCalled, + 'Error handler not triggered while waiting for oidc callback message' + ); assert.strictEqual(this.component.exchangeOIDC.performCount, 0, 'exchangeOIDC method not fired'); + cancelTimers(); + await settled(); }); test('it should ignore untrusted messages while waiting for oidc callback', async function (assert) { @@ -40,10 +46,11 @@ module('Unit | Component | auth-jwt', function (hooks) { this.component.window.trigger('message', { origin: 'http://localhost:4200', isTrusted: false }); assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message'); assert.strictEqual(this.component.exchangeOIDC.performCount, 0, 'exchangeOIDC method not fired'); + cancelTimers(); + await settled(); }); - // TODO: Flaky // test case for https://github.com/hashicorp/vault/issues/12436 test('it should ignore messages sent from outside the app while waiting for oidc callback', async function (assert) { assert.expect(2); @@ -65,12 +72,17 @@ module('Unit | Component | auth-jwt', function (hooks) { message.data.source = 'oidc-callback'; this.component.window.trigger('message', message); - assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message'); + assert.true( + this.errorSpy.notCalled, + 'Error handler not triggered while waiting for oidc callback message' + ); assert.strictEqual( this.component.exchangeOIDC.performCount, 1, 'exchangeOIDC method fires when oidc callback message is received' ); + cancelTimers(); + await settled(); }); }); From e0199efb194a1456dd52faf4fe028597cb079fe4 Mon Sep 17 00:00:00 2001 From: Ryan Cragun Date: Mon, 24 Jun 2024 14:16:07 -0600 Subject: [PATCH 21/24] ui: pin chrome in UI tests (#27580) * ui: pin chrome in UI tests Signed-off-by: Ryan Cragun --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5b14cfe391d..d4504e2bbf10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,6 +165,9 @@ jobs: cache: yarn cache-dependency-path: ui/yarn.lock - uses: browser-actions/setup-chrome@db1b524c26f20a8d1a10f7fc385c92387e2d0477 # v1.7.1 + with: + # Temporarily pin our Chrome version while we sort out a broken test on latest + chrome-version: 1314712 - name: ui-dependencies working-directory: ./ui run: | From c51bdac2c1b400ccfe777fbad772132b29c53bed Mon Sep 17 00:00:00 2001 From: Kuba Wieczorek Date: Tue, 25 Jun 2024 14:09:44 +0100 Subject: [PATCH 22/24] [VAULT-27978] Increase writer request timeout from 500ms to 3s in TestConsulFencing_PartitionedLeaderCantWrite to fix flakiness in CI (#27590) --- .../consul_fencing_test.go | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/vault/external_tests/consul_fencing_binary/consul_fencing_test.go b/vault/external_tests/consul_fencing_binary/consul_fencing_test.go index 852e292f7980..5e3cb9f83b2f 100644 --- a/vault/external_tests/consul_fencing_binary/consul_fencing_test.go +++ b/vault/external_tests/consul_fencing_binary/consul_fencing_test.go @@ -29,18 +29,17 @@ import ( // (and Consul lock improvements) and should _never_ fail now we correctly fence // writes. func TestConsulFencing_PartitionedLeaderCantWrite(t *testing.T) { - t.Skip("Skipping the test due to flakiness, it will be resolved in VAULT-27978.") - - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() consulStorage := consul.NewClusterStorage() - // Create cluster logger that will dump cluster logs to stdout for debugging. + // Create cluster logger that will write cluster logs to a file in CI. logger := corehelpers.NewTestLogger(t) logger.SetLevel(hclog.Trace) clusterOpts := docker.DefaultOptions(t) + // We can use an enterprise image here because we are swapping out the binary anyway. clusterOpts.ImageRepo = "hashicorp/vault-enterprise" clusterOpts.ClusterOptions.Logger = logger @@ -119,7 +118,7 @@ func TestConsulFencing_PartitionedLeaderCantWrite(t *testing.T) { require.NoError(t, err) const interval = 500 * time.Millisecond - + const timeout = 3 * time.Second runWriter := func(i int, targetServer testcluster.VaultClusterNode, ctr *uint64) { wg.Add(1) defer wg.Done() @@ -128,10 +127,13 @@ func TestConsulFencing_PartitionedLeaderCantWrite(t *testing.T) { for { key := fmt.Sprintf("c%d-%08d", i, atomic.LoadUint64(ctr)) - // Use a short timeout. If we don't then the one goroutine writing to the - // partitioned active node can get stuck here until the 60 second request - // timeout kicks in without issuing another request. - reqCtx, cancel := context.WithTimeout(ctx, interval) + // Use a short timeout. If we don't then the one goroutine writing + // to the partitioned active node can get stuck here until the 60 + // second request timeout kicks in without issuing another request. + // However, this timeout being too short can cause issues too. + // Having it set to 500 milliseconds caused the test to + // intermittently fail in CI before. + reqCtx, cancel := context.WithTimeout(ctx, timeout) logger.Debug("sending patch", "client", i, "key", key) _, err = kv.Patch(reqCtx, "data", map[string]interface{}{ key: 1, @@ -217,7 +219,7 @@ func TestConsulFencing_PartitionedLeaderCantWrite(t *testing.T) { logger.Info("failed write", "write_count", writesAfterPartition, "err", err) default: } - require.NoError(t, ctx.Err()) + require.NoError(t, ctx.Err(), "context error while waiting for writes to new leader") } // Heal partition From 93682b05478b9635ca77a7084d5b1915038e9bc2 Mon Sep 17 00:00:00 2001 From: Mike Palmiotto Date: Tue, 25 Jun 2024 10:41:56 -0400 Subject: [PATCH 23/24] Prepare CE changes for [census.Agent] SetMetadata (#27577) --- command/server.go | 2 +- vault/activity_log.go | 3 ++- vault/census.go | 1 - vault/census_stubs_oss.go | 3 ++- vault/core.go | 14 +++++--------- vault/logical_system_activity.go | 11 ++++------- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/command/server.go b/command/server.go index 661264dce0e2..35ced21ffd8c 100644 --- a/command/server.go +++ b/command/server.go @@ -1711,7 +1711,7 @@ func (c *ServerCommand) Run(args []string) int { sr.NotifyConfigurationReload(srConfig) } - if err := core.ReloadCensus(); err != nil { + if err := core.ReloadCensusManager(); err != nil { c.UI.Error(err.Error()) } diff --git a/vault/activity_log.go b/vault/activity_log.go index 9aab39684be0..28a56d44e92d 100644 --- a/vault/activity_log.go +++ b/vault/activity_log.go @@ -1016,7 +1016,8 @@ func (a *ActivityLog) SetConfigInit(config activityConfig) { a.defaultReportMonths = config.DefaultReportMonths a.retentionMonths = config.RetentionMonths - if a.retentionMonths < a.configOverrides.MinimumRetentionMonths { + // Let tests override the minimum if they want to. + if a.configOverrides.MinimumRetentionMonths > 0 { a.retentionMonths = a.configOverrides.MinimumRetentionMonths } diff --git a/vault/census.go b/vault/census.go index e492ac2c88c3..fc1cf2b10851 100644 --- a/vault/census.go +++ b/vault/census.go @@ -14,7 +14,6 @@ func (c *Core) setupCensusManager() error { return nil } func (c *Core) BillingStart() time.Time { return time.Time{} } func (c *Core) AutomatedLicenseReportingEnabled() bool { return false } func (c *Core) CensusAgent() CensusReporter { return nil } -func (c *Core) ReloadCensus() error { return nil } func (c *Core) teardownCensusManager() error { return nil } func (c *Core) StartManualCensusSnapshots() {} func (c *Core) ManualLicenseReportingEnabled() bool { return false } diff --git a/vault/census_stubs_oss.go b/vault/census_stubs_oss.go index 3cf43c495bf7..0b5b1c11f0e6 100644 --- a/vault/census_stubs_oss.go +++ b/vault/census_stubs_oss.go @@ -10,4 +10,5 @@ import "context" //go:generate go run github.com/hashicorp/vault/tools/stubmaker func (c *Core) StartCensusReports(ctx context.Context) {} -func (c *Core) ReloadCensusActivityLog() error { return nil } +func (c *Core) SetRetentionMonths(months int) error { return nil } +func (c *Core) ReloadCensusManager() error { return nil } diff --git a/vault/core.go b/vault/core.go index 18b1a872478d..30b3c284e964 100644 --- a/vault/core.go +++ b/vault/core.go @@ -2445,16 +2445,13 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c return err } - if !c.perfStandby { - if err := c.setupCensusManager(); err != nil { - logger.Error("failed to instantiate the license reporting agent", "error", err) - } - - c.StartCensusReports(ctx) - - c.StartManualCensusSnapshots() + if err := c.setupCensusManager(); err != nil { + logger.Error("failed to instantiate the license reporting agent", "error", err) } + c.StartCensusReports(ctx) + c.StartManualCensusSnapshots() + } else { broker, err := audit.NewBroker(logger) if err != nil { @@ -2847,7 +2844,6 @@ func (c *Core) preSeal() error { if err := c.teardownCensusManager(); err != nil { result = multierror.Append(result, fmt.Errorf("error tearing down reporting agent: %w", err)) } - if err := c.teardownCredentials(context.Background()); err != nil { result = multierror.Append(result, fmt.Errorf("error tearing down credentials: %w", err)) } diff --git a/vault/logical_system_activity.go b/vault/logical_system_activity.go index 38674be00042..92feaf3ca59c 100644 --- a/vault/logical_system_activity.go +++ b/vault/logical_system_activity.go @@ -422,9 +422,6 @@ func (b *SystemBackend) handleActivityConfigUpdate(ctx context.Context, req *log } } - a.core.activityLogLock.RLock() - minimumRetentionMonths := a.configOverrides.MinimumRetentionMonths - a.core.activityLogLock.RUnlock() enabled := config.Enabled == "enable" if !enabled && config.Enabled == "default" { enabled = activityLogEnabledDefault @@ -435,8 +432,8 @@ func (b *SystemBackend) handleActivityConfigUpdate(ctx context.Context, req *log } // if manual license reporting is enabled, retention months must at least be 48 months - if a.core.ManualLicenseReportingEnabled() && config.RetentionMonths < minimumRetentionMonths { - return logical.ErrorResponse("retention_months must be at least %d while Reporting is enabled", minimumRetentionMonths), logical.ErrInvalidRequest + if a.core.ManualLicenseReportingEnabled() && config.RetentionMonths < ActivityLogMinimumRetentionMonths { + return logical.ErrorResponse("retention_months must be at least %d while Reporting is enabled", ActivityLogMinimumRetentionMonths), logical.ErrInvalidRequest } // Store the config @@ -451,9 +448,9 @@ func (b *SystemBackend) handleActivityConfigUpdate(ctx context.Context, req *log // Set the new config on the activity log a.SetConfig(ctx, config) - // reload census agent if retention months change during update when reporting is enabled + // Update Census agent's metadata if retention months change if prevRetentionMonths != config.RetentionMonths { - if err := a.core.ReloadCensusActivityLog(); err != nil { + if err := b.Core.SetRetentionMonths(config.RetentionMonths); err != nil { return nil, err } } From ad1b5dfcd694e34017a2f79d84cfd24cddb3067d Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Tue, 25 Jun 2024 17:36:29 +0100 Subject: [PATCH 24/24] Docs: fix listener 'proxy_protocol_behavior' support version 1 and 2 (#27594) * fix listener 'proxy_protocol_behavior' docs to mention support for v1 and v2 * Update website/content/docs/configuration/listener/tcp/index.mdx Co-authored-by: Violet Hynes --------- Co-authored-by: Violet Hynes --- .../docs/configuration/listener/tcp/index.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/content/docs/configuration/listener/tcp/index.mdx b/website/content/docs/configuration/listener/tcp/index.mdx index db793d6b0017..280c596f4a95 100644 --- a/website/content/docs/configuration/listener/tcp/index.mdx +++ b/website/content/docs/configuration/listener/tcp/index.mdx @@ -55,7 +55,7 @@ drop connection requests from clients using TLS 1.0 or 1.1. Vault uses the following ciphersuites by default: - **TLS 1.3** - `TLS_AES_128_GCM_SHA256`, `TLS_AES_256_GCM_SHA384`, or `TLS_CHACHA20_POLY1305_SHA256`. -- **TLS 1.2** - depends on whether you configure Vault with a RSA or ECDSA certificate. +- **TLS 1.2** - depends on whether you configure Vault with a RSA or ECDSA certificate. You can configure Vault with any cipher supported by the [`tls`](https://pkg.go.dev/crypto/tls) and @@ -162,7 +162,7 @@ default value in the `"/sys/config/ui"` [API endpoint](/vault/api-docs/system/co `default_max_request_duration` for this listener. - `proxy_protocol_behavior` `(string: "")` – When specified, enables a PROXY - protocol version 1 behavior for the listener. + protocol behavior for the listener (version 1 and 2 are both supported). Accepted Values: - _use_always_ - The client's IP address will always be used. @@ -245,7 +245,7 @@ default value in the `"/sys/config/ui"` [API endpoint](/vault/api-docs/system/co used for checking the authenticity of client. - `tls_disable_client_certs` `(string: "false")` – Turns off client - authentication for this listener. The default behavior (when this is false) + authentication for this listener. The default behavior (when this is false) is for Vault to request client authentication certificates when available. ~> **Warning**: The `tls_disable_client_certs` and `tls_require_and_verify_client_cert` fields in the listener stanza of the Vault server configuration are mutually exclusive fields. Please ensure they are not both set to true. TLS client verification remains optional with default settings and is not enforced. @@ -261,19 +261,19 @@ default value in the `"/sys/config/ui"` [API endpoint](/vault/api-docs/system/co - `x_forwarded_for_client_cert_header` `(string: "")` – Specifies the header that will be used for the client certificate. - This is required if you use the [TLS Certificates Auth Method](/vault/docs/auth/cert) and your + This is required if you use the [TLS Certificates Auth Method](/vault/docs/auth/cert) and your vault server is behind a reverse proxy. - `x_forwarded_for_client_cert_header_decoders` `(string: "")` – Comma delimited list that specifies the decoders that will be used to decode the client certificate. - This is required if you use the [TLS Certificates Auth Method](/vault/docs/auth/cert) and your + This is required if you use the [TLS Certificates Auth Method](/vault/docs/auth/cert) and your vault server is behind a reverse proxy. The resulting certificate should be in DER format. Available Values: - BASE64 - Runs Base64 decode - DER - Converts a pem certificate to der - URL - Runs URL decode - + Known Values: - Traefik = "BASE64" @@ -605,4 +605,4 @@ Raft Applied Index 219 [golang-tls]: https://golang.org/src/crypto/tls/cipher_suites.go [api-addr]: /vault/docs/configuration#api_addr [cluster-addr]: /vault/docs/configuration#cluster_addr -[go-tls-blog]: https://go.dev/blog/tls-cipher-suites \ No newline at end of file +[go-tls-blog]: https://go.dev/blog/tls-cipher-suites