From e2b682ec346bec04481ba50661d3ecbff0032966 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Tue, 23 Jan 2024 15:38:17 +0000 Subject: [PATCH 01/14] feat(sdk/go): add kubernetes client token provider --- go.work.sum | 50 +++++++++ internal/cmd/protoc-gen-go-flipt-sdk/main.go | 106 ++++++++++++++++-- sdk/go/sdk.gen.go | 111 +++++++++++++++++-- 3 files changed, 251 insertions(+), 16 deletions(-) diff --git a/go.work.sum b/go.work.sum index c9629df08b..6198d18f4d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,6 +1,8 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512 h1:SRsZGA7aFnCZETmov57jwPrWuTmaZK6+4R4v5FUe1/c= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1 h1:tdpHgTbmbvEIARu+bixzmleMi14+3imnpoFXz+Qzjp4= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -18,9 +20,11 @@ cloud.google.com/go/aiplatform v1.54.0 h1:wH7OYl9Vq/5tupok0BPTFY9xaTLb0GxkReHtB5 cloud.google.com/go/aiplatform v1.54.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/aiplatform v1.57.0 h1:WcZ6wDf/1qBWatmGM9Z+2BTiNjQQX54k2BekHUj93DQ= cloud.google.com/go/aiplatform v1.57.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.58.0 h1:xyCAfpI4yUMOQ4VtHN/bdmxPQ8xoEkTwFM1nbVmuQhs= cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/analytics v0.21.6 h1:fnV7B8lqyEYxCU0LKk+vUL7mTlqRAq4uFlIthIdr/iA= cloud.google.com/go/analytics v0.21.6/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= +cloud.google.com/go/analytics v0.22.0 h1:w8KIgW8NRUHFVKjpkwCpLaHsr685tJ+ckPStOaSCZz0= cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= cloud.google.com/go/apigateway v1.6.4 h1:VVIxCtVerchHienSlaGzV6XJGtEM9828Erzyr3miUGs= cloud.google.com/go/apigateway v1.6.4/go.mod h1:0EpJlVGH5HwAN4VF4Iec8TAzGN1aQgbxAWGJsnPCGGY= @@ -36,6 +40,7 @@ cloud.google.com/go/artifactregistry v1.14.6 h1:/hQaadYytMdA5zBh+RciIrXZQBWK4vN7 cloud.google.com/go/artifactregistry v1.14.6/go.mod h1:np9LSFotNWHcjnOgh8UVK0RFPCTUGbO0ve3384xyHfE= cloud.google.com/go/asset v1.15.3 h1:uI8Bdm81s0esVWbWrTHcjFDFKNOa9aB7rI1vud1hO84= cloud.google.com/go/asset v1.15.3/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.0 h1:dLWfTnbwyrq/Kt8Tr2JiAbre1MEvS2Bl5cAMiYAy5Pg= cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= cloud.google.com/go/assuredworkloads v1.11.4 h1:FsLSkmYYeNuzDm8L4YPfLWV+lQaUrJmH5OuD37t1k20= cloud.google.com/go/assuredworkloads v1.11.4/go.mod h1:4pwwGNwy1RP0m+y12ef3Q/8PaiWrIDQ6nD2E8kvWI9U= @@ -63,6 +68,7 @@ cloud.google.com/go/certificatemanager v1.7.4 h1:5YMQ3Q+dqGpwUZ9X5sipsOQ1fLPsxod cloud.google.com/go/certificatemanager v1.7.4/go.mod h1:FHAylPe/6IIKuaRmHbjbdLhGhVQ+CWHSD5Jq0k4+cCE= cloud.google.com/go/channel v1.17.3 h1:Rd4+fBrjiN6tZ4TR8R/38elkyEkz6oogGDr7jDyjmMY= cloud.google.com/go/channel v1.17.3/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= +cloud.google.com/go/channel v1.17.4 h1:yYHOORIM+wkBy3EdwArg/WL7Lg+SoGzlKH9o3Bw2/jE= cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= cloud.google.com/go/cloudbuild v1.14.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= cloud.google.com/go/cloudbuild v1.14.2/go.mod h1:Bn6RO0mBYk8Vlrt+8NLrru7WXlQ9/RDWz2uo5KG1/sg= @@ -110,6 +116,7 @@ cloud.google.com/go/dataplex v1.11.2 h1:AfFFR15Ifh4U+Me1IBztrSd5CrasTODzy3x8KtDy cloud.google.com/go/dataplex v1.11.2/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataplex v1.13.0 h1:ACVOuxwe7gP0SqEso9SLyXbcZNk5l8hjcTX+XLntI5s= cloud.google.com/go/dataplex v1.13.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.0 h1:/WhVTR4v/L6ACKjlz/9CqkxkrVh2z7C44CLMUf0f60A= cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataproc v1.12.0 h1:W47qHL3W4BPkAIbk4SWmIERwsWBaNnWm0P2sdx3YgGU= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= @@ -133,6 +140,7 @@ cloud.google.com/go/dialogflow v1.44.3 h1:cK/f88KX+YVR4tLH4clMQlvrLWD2qmKJQziusj cloud.google.com/go/dialogflow v1.44.3/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= cloud.google.com/go/dialogflow v1.47.0 h1:tLCWad8HZhlyUNfDzDP5m+oH6h/1Uvw/ei7B9AnsWMk= cloud.google.com/go/dialogflow v1.47.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.0 h1:I7UsYowDdNhFI7RCix1uoThDp+8ULHByOo4n1T96y1A= cloud.google.com/go/dialogflow v1.48.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= cloud.google.com/go/dlp v1.11.1 h1:OFlXedmPP/5//X1hBEeq3D9kUVm9fb6ywYANlpv/EsQ= cloud.google.com/go/dlp v1.11.1/go.mod h1:/PA2EnioBeXTL/0hInwgj0rfsQb3lpE3R8XUJxqUNKI= @@ -140,6 +148,7 @@ cloud.google.com/go/documentai v1.23.5 h1:KAlzT+q8qvRxAmhsJUvLtfFHH0PNvz3M79H6Cg cloud.google.com/go/documentai v1.23.5/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/documentai v1.23.6 h1:0/S3AhS23+0qaFe3tkgMmS3STxgDgmE1jg4TvaDOZ9g= cloud.google.com/go/documentai v1.23.6/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.7 h1:hlYieOXUwiJ7HpBR/vEPfr8nfSxveLVzbqbUkSK0c/4= cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/domains v0.9.4 h1:ua4GvsDztZ5F3xqjeLKVRDeOvJshf5QFgWGg1CKti3A= cloud.google.com/go/domains v0.9.4/go.mod h1:27jmJGShuXYdUNjyDG0SodTfT5RwLi7xmH334Gvi3fY= @@ -168,6 +177,7 @@ cloud.google.com/go/gkehub v0.14.4 h1:J5tYUtb3r0cl2mM7+YHvV32eL+uZQ7lONyUZnPikCE cloud.google.com/go/gkehub v0.14.4/go.mod h1:Xispfu2MqnnFt8rV/2/3o73SK1snL8s9dYJ9G2oQMfc= cloud.google.com/go/gkemulticloud v1.0.3 h1:NmJsNX9uQ2CT78957xnjXZb26TDIMvv+d5W2vVUt0Pg= cloud.google.com/go/gkemulticloud v1.0.3/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= +cloud.google.com/go/gkemulticloud v1.1.0 h1:C2Suwn3uPz+Yy0bxVjTlsMrUCaDovkgvfdyIa+EnUOU= cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= cloud.google.com/go/grafeas v0.3.0 h1:oyTL/KjiUeBs9eYLw/40cpSZglUC+0F7X4iu/8t7NWs= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= @@ -189,6 +199,7 @@ cloud.google.com/go/lifesciences v0.9.4 h1:rZEI/UxcxVKEzyoRS/kdJ1VoolNItRWjNN0Uk cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqNj+Nia7hF0Z7JA= cloud.google.com/go/logging v1.8.1 h1:26skQWPeYhvIasWKm48+Eq7oUqdcdbwsCVwz5Ys0FvU= cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= +cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= @@ -207,6 +218,7 @@ cloud.google.com/go/metastore v1.13.3 h1:94l/Yxg9oBZjin2bzI79oK05feYefieDq0o5fjL cloud.google.com/go/metastore v1.13.3/go.mod h1:K+wdjXdtkdk7AQg4+sXS8bRrQa9gcOr+foOMF2tqINE= cloud.google.com/go/monitoring v1.16.3 h1:mf2SN9qSoBtIgiMA4R/y4VADPWZA7VCNJA079qLaZQ8= cloud.google.com/go/monitoring v1.16.3/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.17.0 h1:blrdvF0MkPPivSO041ihul7rFMhXdVp8Uq7F59DKXTU= cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= cloud.google.com/go/networkconnectivity v1.14.3 h1:e9lUkCe2BexsqsUc2bjV8+gFBpQa54J+/F3qKVtW+wA= cloud.google.com/go/networkconnectivity v1.14.3/go.mod h1:4aoeFdrJpYEXNvrnfyD5kIzs8YtHg945Og4koAjHQek= @@ -222,6 +234,7 @@ cloud.google.com/go/orchestration v1.8.4 h1:kgwZ2f6qMMYIVBtUGGoU8yjYWwMTHDanLwM/ cloud.google.com/go/orchestration v1.8.4/go.mod h1:d0lywZSVYtIoSZXb0iFjv9SaL13PGyVOKDxqGxEf/qI= cloud.google.com/go/orgpolicy v1.11.4 h1:RWuXQDr9GDYhjmrredQJC7aY7cbyqP9ZuLbq5GJGves= cloud.google.com/go/orgpolicy v1.11.4/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= +cloud.google.com/go/orgpolicy v1.12.0 h1:sab7cDiyfdthpAL0JkSpyw1C3mNqkXToVOhalm79PJQ= cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= cloud.google.com/go/osconfig v1.12.4 h1:OrRCIYEAbrbXdhm13/JINn9pQchvTTIzgmOCA7uJw8I= cloud.google.com/go/osconfig v1.12.4/go.mod h1:B1qEwJ/jzqSRslvdOCI8Kdnp0gSng0xW4LOnIebQomA= @@ -247,6 +260,7 @@ cloud.google.com/go/recommendationengine v0.8.4 h1:JRiwe4hvu3auuh2hujiTc2qNgPPfV cloud.google.com/go/recommendationengine v0.8.4/go.mod h1:GEteCf1PATl5v5ZsQ60sTClUE0phbWmo3rQ1Js8louU= cloud.google.com/go/recommender v1.11.3 h1:VndmgyS/J3+izR8V8BHa7HV/uun8//ivQ3k5eVKKyyM= cloud.google.com/go/recommender v1.11.3/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= +cloud.google.com/go/recommender v1.12.0 h1:tC+ljmCCbuZ/ybt43odTFlay91n/HLIhflvaOeb0Dh4= cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= cloud.google.com/go/redis v1.14.1 h1:J9cEHxG9YLmA9o4jTSvWt/RuVEn6MTrPlYSCRHujxDQ= cloud.google.com/go/redis v1.14.1/go.mod h1:MbmBxN8bEnQI4doZPC1BzADU4HGocHBk2de3SbgOkqs= @@ -280,6 +294,7 @@ cloud.google.com/go/spanner v1.53.0 h1:/NzWQJ1MEhdRcffiutRKbW/AIGVKhcTeivWTDjEyC cloud.google.com/go/spanner v1.53.0/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= cloud.google.com/go/spanner v1.53.1 h1:xNmE0SXMSxNBuk7lRZ5G/S+A49X91zkSTt7Jn5Ptlvw= cloud.google.com/go/spanner v1.53.1/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.54.0 h1:ttU+lhARPF/iZE3OkCpmfsemCz9mLaqBhGPd3Qub2sQ= cloud.google.com/go/spanner v1.54.0/go.mod h1:wZvSQVBgngF0Gq86fKup6KIYmN2be7uOKjtK97X+bQU= cloud.google.com/go/speech v1.20.1 h1:OpJ666ao7XxXewGSAkDUJnW188tJ5hNPoM7pZB+Q730= cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= @@ -299,6 +314,7 @@ cloud.google.com/go/trace v1.10.4 h1:2qOAuAzNezwW3QN+t41BtkDJOG42HywL73q8x/f6fnM cloud.google.com/go/trace v1.10.4/go.mod h1:Nso99EDIK8Mj5/zmB+iGr9dosS/bzWCJ8wGmE6TXNWY= cloud.google.com/go/translate v1.9.3 h1:t5WXTqlrk8VVJu/i3WrYQACjzYJiff5szARHiyqqPzI= cloud.google.com/go/translate v1.9.3/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= +cloud.google.com/go/translate v1.10.0 h1:tncNaKmlZnayMMRX/mMM2d5AJftecznnxVBD4w070NI= cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= cloud.google.com/go/video v1.20.3 h1:Xrpbm2S9UFQ1pZEeJt9Vqm5t2T/z9y/M3rNXhFoo8Is= cloud.google.com/go/video v1.20.3/go.mod h1:TnH/mNZKVHeNtpamsSPygSR0iHtvrR/cW1/GDjN5+GU= @@ -525,6 +541,8 @@ github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1l github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bufbuild/protovalidate-go v0.2.1 h1:pJr07sYhliyfj/STAM7hU4J3FKpVeLVKvOBmOTN8j+s= +github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4cEMKCSSb8ZCkin+MVA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= @@ -978,6 +996,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= +github.com/google/cel-go v0.17.1 h1:s2151PDGy/eqpCI80/8dl4VL3xTkqI/YubXLXCFw0mw= +github.com/google/cel-go v0.17.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -1404,6 +1424,7 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d h1:zapSxdmZYY6vJWXFKLQ+MkI+agc+HQyfrCGowDSHiKs= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= @@ -1558,6 +1579,7 @@ github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOH github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -2005,9 +2027,15 @@ gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8= +gorm.io/gorm v1.21.4 h1:J0xfPJMRfHgpVcYLrEAIqY/apdvTIkrltPQNHQLq9Qc= +k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= +k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= +k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/code-generator v0.19.7 h1:kM/68Y26Z/u//TFc1ggVVcg62te8A2yQh57jBfD0FWQ= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= @@ -2017,6 +2045,7 @@ k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMY k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= +k8s.io/cri-api v0.27.1 h1:KWO+U8MfI9drXB/P4oU9VchaWYOlwDglJZVHWMpTT3Q= k8s.io/cri-api v0.27.1/go.mod h1:+Ts/AVYbIo04S86XbTD73UPp/DkTiYxtsFeOFEu32L0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -2044,25 +2073,45 @@ k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/b v1.0.0 h1:vpvqeyp17ddcQWF29Czawql4lDdABCDRbXRAS4+aF2o= modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= +modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/db v1.0.0 h1:2c6NdCfaLnshSvY7OU09cyAY0gYXUZj4lmg5ItHyucg= modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= +modernc.org/file v1.0.0 h1:9/PdvjVxd5+LcWUQIfapAWRGOkDLK90rloa8s/au06A= modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= +modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= +modernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw= modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= +modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/lldb v1.0.0 h1:6vjDJxQEfhlOLwl4bhpwIz00uyFK4EmSYcbwqwbynsc= modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/ql v1.0.0 h1:bIQ/trWNVjQPlinI6jdOQsi195SIturGo3mp5hsDqVU= modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= +modernc.org/sortutil v1.1.0 h1:oP3U4uM+NT/qBQcbg/K2iqAX0Nx7B1b6YZtq3Gk/PjM= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= +modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/zappy v1.0.0 h1:dPVaP+3ueIUv4guk8PuZ2wiUGcJ1WUVvIheeSSTD0yk= modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= @@ -2078,4 +2127,5 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 h1:+xBL5uTc+BkPB sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= diff --git a/internal/cmd/protoc-gen-go-flipt-sdk/main.go b/internal/cmd/protoc-gen-go-flipt-sdk/main.go index 46092e33d9..460d140a8d 100644 --- a/internal/cmd/protoc-gen-go-flipt-sdk/main.go +++ b/internal/cmd/protoc-gen-go-flipt-sdk/main.go @@ -58,6 +58,16 @@ func generateSDK(gen *protogen.Plugin) { g.P() g.P("package sdk") g.P() + g.P("var _ *", importPackage(g, "time")("Time")) + g.P("var _ *", importPackage(g, "os")("File")) + g.P("var _ *", importPackage(g, "sync")("Mutex")) + g.P("var _ ", importPackage(g, "go.flipt.io/flipt/rpc/flipt/auth")("Method")) + g.P() + g.P("const (") + g.P(`defaultServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"`) + g.P(`defaultKubernetesExpiryLeeway = 10 * time.Second`) + g.P(")") + g.P() g.P("type Transport interface {") var types [][2]string for _, file := range gen.Files { @@ -190,7 +200,7 @@ func authenticateFunction(g *protogen.GeneratedFile) { g.P("func authenticate(ctx ", context("Context"), ", p ClientAuthenticationProvider) (", context("Context"), ", error) {") metadata := importPackage(g, "google.golang.org/grpc/metadata") g.P("if p != nil {") - g.P("authentication, err := p.Authentication()") + g.P("authentication, err := p.Authentication(ctx)") g.P("if err != nil { return ctx, err }") g.P() g.P("ctx = ", metadata("AppendToOutgoingContext"), `(ctx, "authorization", authentication)`) @@ -233,7 +243,7 @@ type ClientTokenProvider interface { // Deprecated: Use WithAuthenticationProvider instead. func WithClientTokenProvider(p ClientTokenProvider) Option { return func(s *SDK) { - s.authenticationProvider = authenticationProviderFunc(func() (string, error) { + s.authenticationProvider = authenticationProviderFunc(func(context.Context) (string, error) { clientToken, err := p.ClientToken() if err != nil { return "", err @@ -244,10 +254,10 @@ func WithClientTokenProvider(p ClientTokenProvider) Option { } } -type authenticationProviderFunc func() (string, error) +type authenticationProviderFunc func(context.Context) (string, error) -func (f authenticationProviderFunc) Authentication() (string, error) { - return f() +func (f authenticationProviderFunc) Authentication(ctx context.Context) (string, error) { + return f(ctx) } // StaticClientTokenProvider is a string which is supplied as a static client token @@ -265,7 +275,7 @@ func (p StaticClientTokenProvider) ClientToken() (string, error) { // client authentication which can be used to authenticate RPC/API calls // invoked through the SDK. type ClientAuthenticationProvider interface { - Authentication() (string, error) + Authentication(context.Context) (string, error) } // SDK is the definition of Flipt's Go SDK. @@ -294,7 +304,7 @@ func WithAuthenticationProvider(p ClientAuthenticationProvider) Option { type StaticTokenAuthenticationProvider string // Authentication returns the underlying string that is the StaticTokenAuthenticationProvider. -func (p StaticTokenAuthenticationProvider) Authentication() (string, error) { +func (p StaticTokenAuthenticationProvider) Authentication(context.Context) (string, error) { return "Bearer " + string(p), nil } @@ -303,10 +313,90 @@ func (p StaticTokenAuthenticationProvider) Authentication() (string, error) { type JWTAuthenticationProvider string // Authentication returns the underlying string that is the JWTAuthenticationProvider. -func (p JWTAuthenticationProvider) Authentication() (string, error) { +func (p JWTAuthenticationProvider) Authentication(context.Context) (string, error) { return "JWT " + string(p), nil } +// KubernetesClientTokenProvider is an implementation of ClientAuthenticationProvider +// which automatically uses the service account token from the environment and exchanges +// it with Flipt for a client token. +// This provider keeps the client token up to date and refreshes it for a new client +// token before expiry. It re-reads the service account token as Kubernetes can and will refresh +// this token, as it also has its own expiry. +type KubernetesClientTokenProvider struct { + transport Transport + serviceAccountTokenPath string + leeway time.Duration + + mu sync.RWMutex + resp *auth.VerifyServiceAccountResponse +} + +// KubernetesClientTokenProviderOption is a functional option for configuring KubernetesClientTokenProvider. +type KubernetesClientTokenProviderOption func(*KubernetesClientTokenProvider) + +// WithKubernetesServiceAccountTokenPath sets the path on the host to locate the kubernetes service account. +// The KubernetesClientTokenProvider uses the default location set by Kubernetes. +// This option lets you override that if your path happens to differ. +func WithKubernetesServiceAccountTokenPath(p string) KubernetesClientTokenProviderOption { + return func(kctp *KubernetesClientTokenProvider) { + kctp.serviceAccountTokenPath = p + } +} + +// WithKubernetesExpiryLeeway configures the duration leeway for deciding when to refresh +// the client token. The default is 10 seconds, which ensures that tokens are automatically refreshed +// when their is less that 10 seconds of lifetime left on the previously fetched client token. +func WithKubernetesExpiryLeeway(d time.Duration) KubernetesClientTokenProviderOption { + return func(kctp *KubernetesClientTokenProvider) { + kctp.leeway = d + } +} + +// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesClientTokenProvider +// using the provided transport. +func NewKuberntesClientTokenProvider(transport Transport) *KubernetesClientTokenProvider { + return &KubernetesClientTokenProvider{ + transport: transport, + serviceAccountTokenPath: defaultServiceAccountTokenPath, + leeway: defaultKubernetesExpiryLeeway, + } +} + +// Authentication returns the authentication header string to be used for a request +// by the client SDK. It is generated via exchanging the local service account token +// with Flipt for a client token. The token is then formatted appropriately for use +// in the Authentication header as a bearer token. +func (k *KubernetesClientTokenProvider) Authentication(ctx context.Context) (string, error) { + k.mu.RLock() + resp := k.resp + k.mu.RUnlock() + if resp != nil && time.Now().UTC().Add(k.leeway).Before(resp.Authentication.ExpiresAt.AsTime()) { + return StaticTokenAuthenticationProvider(k.resp.ClientToken).Authentication(ctx) + } + + k.mu.Lock() + defer k.mu.Unlock() + saToken, err := os.ReadFile(k.serviceAccountTokenPath) + if err != nil { + return "", err + } + + resp, err = k.transport. + AuthClient(). + AuthenticationMethodKubernetesServiceClient(). + VerifyServiceAccount(ctx, &auth.VerifyServiceAccountRequest{ + ServiceAccountToken: string(saToken), + }) + if err != nil { + return "", err + } + + k.resp = resp + + return StaticTokenAuthenticationProvider(k.resp.ClientToken).Authentication(ctx) +} + // New constructs and configures a Flipt SDK instance from // the provided Transport implementation and options. func New(t Transport, opts ...Option) SDK { diff --git a/sdk/go/sdk.gen.go b/sdk/go/sdk.gen.go index 9fc3b3ca23..50a372c02c 100644 --- a/sdk/go/sdk.gen.go +++ b/sdk/go/sdk.gen.go @@ -4,12 +4,27 @@ package sdk import ( context "context" + os "os" + sync "sync" + time "time" + flipt "go.flipt.io/flipt/rpc/flipt" + auth "go.flipt.io/flipt/rpc/flipt/auth" evaluation "go.flipt.io/flipt/rpc/flipt/evaluation" meta "go.flipt.io/flipt/rpc/flipt/meta" metadata "google.golang.org/grpc/metadata" ) +var _ *time.Time +var _ *os.File +var _ *sync.Mutex +var _ auth.Method + +const ( + defaultServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" + defaultKubernetesExpiryLeeway = 10 * time.Second +) + type Transport interface { AuthClient() AuthClient EvaluationClient() evaluation.EvaluationServiceClient @@ -30,7 +45,7 @@ type ClientTokenProvider interface { // Deprecated: Use WithAuthenticationProvider instead. func WithClientTokenProvider(p ClientTokenProvider) Option { return func(s *SDK) { - s.authenticationProvider = authenticationProviderFunc(func() (string, error) { + s.authenticationProvider = authenticationProviderFunc(func(context.Context) (string, error) { clientToken, err := p.ClientToken() if err != nil { return "", err @@ -41,10 +56,10 @@ func WithClientTokenProvider(p ClientTokenProvider) Option { } } -type authenticationProviderFunc func() (string, error) +type authenticationProviderFunc func(context.Context) (string, error) -func (f authenticationProviderFunc) Authentication() (string, error) { - return f() +func (f authenticationProviderFunc) Authentication(ctx context.Context) (string, error) { + return f(ctx) } // StaticClientTokenProvider is a string which is supplied as a static client token @@ -62,7 +77,7 @@ func (p StaticClientTokenProvider) ClientToken() (string, error) { // client authentication which can be used to authenticate RPC/API calls // invoked through the SDK. type ClientAuthenticationProvider interface { - Authentication() (string, error) + Authentication(context.Context) (string, error) } // SDK is the definition of Flipt's Go SDK. @@ -91,7 +106,7 @@ func WithAuthenticationProvider(p ClientAuthenticationProvider) Option { type StaticTokenAuthenticationProvider string // Authentication returns the underlying string that is the StaticTokenAuthenticationProvider. -func (p StaticTokenAuthenticationProvider) Authentication() (string, error) { +func (p StaticTokenAuthenticationProvider) Authentication(context.Context) (string, error) { return "Bearer " + string(p), nil } @@ -100,10 +115,90 @@ func (p StaticTokenAuthenticationProvider) Authentication() (string, error) { type JWTAuthenticationProvider string // Authentication returns the underlying string that is the JWTAuthenticationProvider. -func (p JWTAuthenticationProvider) Authentication() (string, error) { +func (p JWTAuthenticationProvider) Authentication(context.Context) (string, error) { return "JWT " + string(p), nil } +// KubernetesClientTokenProvider is an implementation of ClientAuthenticationProvider +// which automatically uses the service account token from the environment and exchanges +// it with Flipt for a client token. +// This provider keeps the client token up to date and refreshes it for a new client +// token before expiry. It re-reads the service account token as Kubernetes can and will refresh +// this token, as it also has its own expiry. +type KubernetesClientTokenProvider struct { + transport Transport + serviceAccountTokenPath string + leeway time.Duration + + mu sync.RWMutex + resp *auth.VerifyServiceAccountResponse +} + +// KubernetesClientTokenProviderOption is a functional option for configuring KubernetesClientTokenProvider. +type KubernetesClientTokenProviderOption func(*KubernetesClientTokenProvider) + +// WithKubernetesServiceAccountTokenPath sets the path on the host to locate the kubernetes service account. +// The KubernetesClientTokenProvider uses the default location set by Kubernetes. +// This option lets you override that if your path happens to differ. +func WithKubernetesServiceAccountTokenPath(p string) KubernetesClientTokenProviderOption { + return func(kctp *KubernetesClientTokenProvider) { + kctp.serviceAccountTokenPath = p + } +} + +// WithKubernetesExpiryLeeway configures the duration leeway for deciding when to refresh +// the client token. The default is 10 seconds, which ensures that tokens are automatically refreshed +// when their is less that 10 seconds of lifetime left on the previously fetched client token. +func WithKubernetesExpiryLeeway(d time.Duration) KubernetesClientTokenProviderOption { + return func(kctp *KubernetesClientTokenProvider) { + kctp.leeway = d + } +} + +// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesClientTokenProvider +// using the provided transport. +func NewKuberntesClientTokenProvider(transport Transport) *KubernetesClientTokenProvider { + return &KubernetesClientTokenProvider{ + transport: transport, + serviceAccountTokenPath: defaultServiceAccountTokenPath, + leeway: defaultKubernetesExpiryLeeway, + } +} + +// Authentication returns the authentication header string to be used for a request +// by the client SDK. It is generated via exchanging the local service account token +// with Flipt for a client token. The token is then formatted appropriately for use +// in the Authentication header as a bearer token. +func (k *KubernetesClientTokenProvider) Authentication(ctx context.Context) (string, error) { + k.mu.RLock() + resp := k.resp + k.mu.RUnlock() + if resp != nil && time.Now().UTC().Add(k.leeway).Before(resp.Authentication.ExpiresAt.AsTime()) { + return StaticTokenAuthenticationProvider(k.resp.ClientToken).Authentication(ctx) + } + + k.mu.Lock() + defer k.mu.Unlock() + saToken, err := os.ReadFile(k.serviceAccountTokenPath) + if err != nil { + return "", err + } + + resp, err = k.transport. + AuthClient(). + AuthenticationMethodKubernetesServiceClient(). + VerifyServiceAccount(ctx, &auth.VerifyServiceAccountRequest{ + ServiceAccountToken: string(saToken), + }) + if err != nil { + return "", err + } + + k.resp = resp + + return StaticTokenAuthenticationProvider(k.resp.ClientToken).Authentication(ctx) +} + // New constructs and configures a Flipt SDK instance from // the provided Transport implementation and options. func New(t Transport, opts ...Option) SDK { @@ -146,7 +241,7 @@ func (s SDK) Meta() *Meta { func authenticate(ctx context.Context, p ClientAuthenticationProvider) (context.Context, error) { if p != nil { - authentication, err := p.Authentication() + authentication, err := p.Authentication(ctx) if err != nil { return ctx, err } From 589302fed84865f8882e689cfa33f8958dfe9379 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Tue, 23 Jan 2024 15:40:53 +0000 Subject: [PATCH 02/14] refactor(cmd/protoc-gen-go-flipt): rename KubernetesClientTokenProvider to KuberneteAuthenticationProvider --- internal/cmd/protoc-gen-go-flipt-sdk/main.go | 26 +++++++-------- sdk/go/sdk.gen.go | 33 ++++++++++---------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/internal/cmd/protoc-gen-go-flipt-sdk/main.go b/internal/cmd/protoc-gen-go-flipt-sdk/main.go index 460d140a8d..123122869e 100644 --- a/internal/cmd/protoc-gen-go-flipt-sdk/main.go +++ b/internal/cmd/protoc-gen-go-flipt-sdk/main.go @@ -317,13 +317,13 @@ func (p JWTAuthenticationProvider) Authentication(context.Context) (string, erro return "JWT " + string(p), nil } -// KubernetesClientTokenProvider is an implementation of ClientAuthenticationProvider +// KubernetesAuthenticationProvider is an implementation of ClientAuthenticationProvider // which automatically uses the service account token from the environment and exchanges // it with Flipt for a client token. // This provider keeps the client token up to date and refreshes it for a new client // token before expiry. It re-reads the service account token as Kubernetes can and will refresh // this token, as it also has its own expiry. -type KubernetesClientTokenProvider struct { +type KubernetesAuthenticationProvider struct { transport Transport serviceAccountTokenPath string leeway time.Duration @@ -332,14 +332,14 @@ type KubernetesClientTokenProvider struct { resp *auth.VerifyServiceAccountResponse } -// KubernetesClientTokenProviderOption is a functional option for configuring KubernetesClientTokenProvider. -type KubernetesClientTokenProviderOption func(*KubernetesClientTokenProvider) +// KubernetesAuthenticationProviderOption is a functional option for configuring KubernetesAuthenticationProvider. +type KubernetesAuthenticationProviderOption func(*KubernetesAuthenticationProvider) // WithKubernetesServiceAccountTokenPath sets the path on the host to locate the kubernetes service account. -// The KubernetesClientTokenProvider uses the default location set by Kubernetes. +// The KubernetesAuthenticationProvider uses the default location set by Kubernetes. // This option lets you override that if your path happens to differ. -func WithKubernetesServiceAccountTokenPath(p string) KubernetesClientTokenProviderOption { - return func(kctp *KubernetesClientTokenProvider) { +func WithKubernetesServiceAccountTokenPath(p string) KubernetesAuthenticationProviderOption { + return func(kctp *KubernetesAuthenticationProvider) { kctp.serviceAccountTokenPath = p } } @@ -347,16 +347,16 @@ func WithKubernetesServiceAccountTokenPath(p string) KubernetesClientTokenProvid // WithKubernetesExpiryLeeway configures the duration leeway for deciding when to refresh // the client token. The default is 10 seconds, which ensures that tokens are automatically refreshed // when their is less that 10 seconds of lifetime left on the previously fetched client token. -func WithKubernetesExpiryLeeway(d time.Duration) KubernetesClientTokenProviderOption { - return func(kctp *KubernetesClientTokenProvider) { +func WithKubernetesExpiryLeeway(d time.Duration) KubernetesAuthenticationProviderOption { + return func(kctp *KubernetesAuthenticationProvider) { kctp.leeway = d } } -// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesClientTokenProvider +// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesAuthenticationProvider // using the provided transport. -func NewKuberntesClientTokenProvider(transport Transport) *KubernetesClientTokenProvider { - return &KubernetesClientTokenProvider{ +func NewKuberntesClientTokenProvider(transport Transport) *KubernetesAuthenticationProvider { + return &KubernetesAuthenticationProvider{ transport: transport, serviceAccountTokenPath: defaultServiceAccountTokenPath, leeway: defaultKubernetesExpiryLeeway, @@ -367,7 +367,7 @@ func NewKuberntesClientTokenProvider(transport Transport) *KubernetesClientToken // by the client SDK. It is generated via exchanging the local service account token // with Flipt for a client token. The token is then formatted appropriately for use // in the Authentication header as a bearer token. -func (k *KubernetesClientTokenProvider) Authentication(ctx context.Context) (string, error) { +func (k *KubernetesAuthenticationProvider) Authentication(ctx context.Context) (string, error) { k.mu.RLock() resp := k.resp k.mu.RUnlock() diff --git a/sdk/go/sdk.gen.go b/sdk/go/sdk.gen.go index 50a372c02c..1e2d4d75fa 100644 --- a/sdk/go/sdk.gen.go +++ b/sdk/go/sdk.gen.go @@ -4,15 +4,14 @@ package sdk import ( context "context" - os "os" - sync "sync" - time "time" - flipt "go.flipt.io/flipt/rpc/flipt" auth "go.flipt.io/flipt/rpc/flipt/auth" evaluation "go.flipt.io/flipt/rpc/flipt/evaluation" meta "go.flipt.io/flipt/rpc/flipt/meta" metadata "google.golang.org/grpc/metadata" + os "os" + sync "sync" + time "time" ) var _ *time.Time @@ -119,13 +118,13 @@ func (p JWTAuthenticationProvider) Authentication(context.Context) (string, erro return "JWT " + string(p), nil } -// KubernetesClientTokenProvider is an implementation of ClientAuthenticationProvider +// KubernetesAuthenticationProvider is an implementation of ClientAuthenticationProvider // which automatically uses the service account token from the environment and exchanges // it with Flipt for a client token. // This provider keeps the client token up to date and refreshes it for a new client // token before expiry. It re-reads the service account token as Kubernetes can and will refresh // this token, as it also has its own expiry. -type KubernetesClientTokenProvider struct { +type KubernetesAuthenticationProvider struct { transport Transport serviceAccountTokenPath string leeway time.Duration @@ -134,14 +133,14 @@ type KubernetesClientTokenProvider struct { resp *auth.VerifyServiceAccountResponse } -// KubernetesClientTokenProviderOption is a functional option for configuring KubernetesClientTokenProvider. -type KubernetesClientTokenProviderOption func(*KubernetesClientTokenProvider) +// KubernetesAuthenticationProviderOption is a functional option for configuring KubernetesAuthenticationProvider. +type KubernetesAuthenticationProviderOption func(*KubernetesAuthenticationProvider) // WithKubernetesServiceAccountTokenPath sets the path on the host to locate the kubernetes service account. -// The KubernetesClientTokenProvider uses the default location set by Kubernetes. +// The KubernetesAuthenticationProvider uses the default location set by Kubernetes. // This option lets you override that if your path happens to differ. -func WithKubernetesServiceAccountTokenPath(p string) KubernetesClientTokenProviderOption { - return func(kctp *KubernetesClientTokenProvider) { +func WithKubernetesServiceAccountTokenPath(p string) KubernetesAuthenticationProviderOption { + return func(kctp *KubernetesAuthenticationProvider) { kctp.serviceAccountTokenPath = p } } @@ -149,16 +148,16 @@ func WithKubernetesServiceAccountTokenPath(p string) KubernetesClientTokenProvid // WithKubernetesExpiryLeeway configures the duration leeway for deciding when to refresh // the client token. The default is 10 seconds, which ensures that tokens are automatically refreshed // when their is less that 10 seconds of lifetime left on the previously fetched client token. -func WithKubernetesExpiryLeeway(d time.Duration) KubernetesClientTokenProviderOption { - return func(kctp *KubernetesClientTokenProvider) { +func WithKubernetesExpiryLeeway(d time.Duration) KubernetesAuthenticationProviderOption { + return func(kctp *KubernetesAuthenticationProvider) { kctp.leeway = d } } -// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesClientTokenProvider +// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesAuthenticationProvider // using the provided transport. -func NewKuberntesClientTokenProvider(transport Transport) *KubernetesClientTokenProvider { - return &KubernetesClientTokenProvider{ +func NewKuberntesClientTokenProvider(transport Transport) *KubernetesAuthenticationProvider { + return &KubernetesAuthenticationProvider{ transport: transport, serviceAccountTokenPath: defaultServiceAccountTokenPath, leeway: defaultKubernetesExpiryLeeway, @@ -169,7 +168,7 @@ func NewKuberntesClientTokenProvider(transport Transport) *KubernetesClientToken // by the client SDK. It is generated via exchanging the local service account token // with Flipt for a client token. The token is then formatted appropriately for use // in the Authentication header as a bearer token. -func (k *KubernetesClientTokenProvider) Authentication(ctx context.Context) (string, error) { +func (k *KubernetesAuthenticationProvider) Authentication(ctx context.Context) (string, error) { k.mu.RLock() resp := k.resp k.mu.RUnlock() From 27eb92555e38e9db3c026a6ff5f81f4decc39cb3 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Tue, 23 Jan 2024 15:50:11 +0000 Subject: [PATCH 03/14] fix(cmd/protoc-gen-go-flipt-sdk): correct k8s constructor name and thread options --- internal/cmd/protoc-gen-go-flipt-sdk/main.go | 12 +++++++++--- sdk/go/sdk.gen.go | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/internal/cmd/protoc-gen-go-flipt-sdk/main.go b/internal/cmd/protoc-gen-go-flipt-sdk/main.go index 123122869e..1fceef8f91 100644 --- a/internal/cmd/protoc-gen-go-flipt-sdk/main.go +++ b/internal/cmd/protoc-gen-go-flipt-sdk/main.go @@ -353,14 +353,20 @@ func WithKubernetesExpiryLeeway(d time.Duration) KubernetesAuthenticationProvide } } -// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesAuthenticationProvider +// NewKuberntesAuthenticationProvider constructs and configures a new KubernetesAuthenticationProvider // using the provided transport. -func NewKuberntesClientTokenProvider(transport Transport) *KubernetesAuthenticationProvider { - return &KubernetesAuthenticationProvider{ +func NewKuberntesAuthenticationProvider(transport Transport, opts ...KubernetesAuthenticationProviderOption) *KubernetesAuthenticationProvider { + k := &KubernetesAuthenticationProvider{ transport: transport, serviceAccountTokenPath: defaultServiceAccountTokenPath, leeway: defaultKubernetesExpiryLeeway, } + + for _, opt := range opts { + opt(k) + } + + return k } // Authentication returns the authentication header string to be used for a request diff --git a/sdk/go/sdk.gen.go b/sdk/go/sdk.gen.go index 1e2d4d75fa..a6996c628a 100644 --- a/sdk/go/sdk.gen.go +++ b/sdk/go/sdk.gen.go @@ -154,14 +154,20 @@ func WithKubernetesExpiryLeeway(d time.Duration) KubernetesAuthenticationProvide } } -// NewKuberntesClientTokenProvider cosntructs and configures a new KubernetesAuthenticationProvider +// NewKuberntesAuthenticationProvider constructs and configures a new KubernetesAuthenticationProvider // using the provided transport. -func NewKuberntesClientTokenProvider(transport Transport) *KubernetesAuthenticationProvider { - return &KubernetesAuthenticationProvider{ +func NewKuberntesAuthenticationProvider(transport Transport, opts ...KubernetesAuthenticationProviderOption) *KubernetesAuthenticationProvider { + k := &KubernetesAuthenticationProvider{ transport: transport, serviceAccountTokenPath: defaultServiceAccountTokenPath, leeway: defaultKubernetesExpiryLeeway, } + + for _, opt := range opts { + opt(k) + } + + return k } // Authentication returns the authentication header string to be used for a request From f2ff7941e168c3b953910abc709ecba44328a84e Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Tue, 23 Jan 2024 16:36:46 +0000 Subject: [PATCH 04/14] chore(sdk/go): update doc.go to account for KubernetesAuthenticationProvider --- sdk/go/doc.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sdk/go/doc.go b/sdk/go/doc.go index 7f2975b9d5..bd42b444b6 100644 --- a/sdk/go/doc.go +++ b/sdk/go/doc.go @@ -33,11 +33,14 @@ The following is an example of creating an instance of the SDK using the HTTP tr The remote procedure calls mades by this SDK are authenticated via a [ClientAuthenticationProvider] implementation. This can be supplied to [New] via the [WithAuthenticationProvider] option. +Note that each of these methods will only work if the target Flipt server instance has the authentication method enabled. -Currently, there are two implementations: +Currently, there are three implementations: - [StaticTokenAuthenticationProvider]: +This provider sets a static Flipt client token via the Authentication header with the Bearer scheme. + func main() { provider := sdk.StaticTokenAuthenticationProvider("some-flipt-token") client := sdk.New(transport, sdk.WithAuthenticationProvider(provider)) @@ -45,11 +48,22 @@ Currently, there are two implementations: - [JWTAuthenticationProvider]: +This provider sets a pre-generated JSON web-token via the Authentication header with the JWT scheme. + func main() { provider := sdk.JWTAuthenticationProvider("some-flipt-jwt") client := sdk.New(transport, sdk.WithAuthenticationProvider(provider)) } +- [KubernetesAuthenticationProvider]: + +This automatically uses the service account token on the host and exchanges it with Flipt for a Flipt client token credential. The credential is then used to authenticate requests, again via the Authentication header and the Bearer scheme. It ensures that the client token is not-expired and requests fresh tokens automatically without intervention. Use this method to automatically authenticate your application with a Flipt deployed into the same Kubernetes cluster. + + func main() { + provider := sdk.NewKuberntesAuthenticationProvider(transport) + client := sdk.New(transport, sdk.WithAuthenticationProvider(provider)) + } + # SDK Services The Flipt [SDK] is split into four sections [Flipt], [Auth], [Meta], and [Evaluation]. From c8c98c3cc4b5597fc2e88790edaea59381cdc67e Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 10:14:29 +0000 Subject: [PATCH 05/14] test(integration): add cases for k8s authentication --- build/internal/cmd/discover/main.go | 80 ++++++++ build/testing/integration.go | 184 ++++++++++++++++-- build/testing/integration/api/api.go | 8 +- build/testing/integration/integration.go | 19 +- .../integration/readonly/readonly_test.go | 6 +- 5 files changed, 268 insertions(+), 29 deletions(-) create mode 100644 build/internal/cmd/discover/main.go diff --git a/build/internal/cmd/discover/main.go b/build/internal/cmd/discover/main.go new file mode 100644 index 0000000000..3076674a13 --- /dev/null +++ b/build/internal/cmd/discover/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "flag" + "fmt" + "net/http" + "os" + "time" + + "github.com/go-jose/go-jose/v3" + "github.com/hashicorp/cap/oidc" +) + +const ( + openidConfiguration = "/.well-known/openid-configuration" + wellKnownJwks = "/.well-known/jwks.json" + domain = "https://discover.svc" +) + +func main() { + privKeyPath := flag.String("private-key", "", "path to private key") + addr := flag.String("addr", ":443", "Port for OIDC server") + flag.Parse() + + p, err := os.ReadFile(*privKeyPath) + if err != nil { + panic(err) + } + + block, _ := pem.Decode([]byte(p)) + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + panic(err) + } + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + switch r.URL.Path { + case openidConfiguration: + reply := struct { + Issuer string `json:"issuer"` + JWKSURI string `json:"jwks_uri"` + SupportedAlgs []string `json:"id_token_signing_alg_values_supported"` + }{ + Issuer: domain, + JWKSURI: domain + wellKnownJwks, + SupportedAlgs: []string{string(oidc.RS256)}, + } + + if err := json.NewEncoder(w).Encode(&reply); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + case wellKnownJwks: + if err := json.NewEncoder(w).Encode(&jose.JSONWebKeySet{ + Keys: []jose.JSONWebKey{ + { + Key: &priv.PublicKey, + KeyID: fmt.Sprintf("%d", time.Now().Unix()), + }, + }, + }); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + default: + http.Error(w, "not found", http.StatusNotFound) + return + } + }) + + _ = http.ListenAndServeTLS(*addr, "/server.crt", "/server.key", handler) +} diff --git a/build/testing/integration.go b/build/testing/integration.go index 946b93ebad..730acd0a92 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -1,15 +1,18 @@ package testing import ( + "bytes" "context" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" "errors" "fmt" "log" + "math/big" "os" "path" "strings" @@ -99,6 +102,7 @@ const ( staticAuth staticAuthNamespaced jwtAuth + k8sAuth ) func (a authConfig) enabled() bool { @@ -111,6 +115,8 @@ func (a authConfig) method() string { return "static" case jwtAuth: return "jwt" + case k8sAuth: + return "k8s" default: return "" } @@ -135,7 +141,7 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger for _, namespace := range []string{"", "production"} { for protocol, port := range protocolPorts { - for _, auth := range []authConfig{noAuth, staticAuth, staticAuthNamespaced, jwtAuth} { + for _, auth := range []authConfig{noAuth, staticAuth, staticAuthNamespaced, jwtAuth, k8sAuth} { auth := auth config := testConfig{ name: fmt.Sprintf("%s namespace %s", strings.ToUpper(protocol), namespace), @@ -154,6 +160,8 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger config.name = fmt.Sprintf("%s with static auth namespaced token", config.name) case jwtAuth: config.name = fmt.Sprintf("%s with jwt auth", config.name) + case k8sAuth: + config.name = fmt.Sprintf("%s with k8s auth", config.name) } configs = append(configs, config) @@ -168,28 +176,52 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger var ( fn = fn config = config + flipt = flipt + base = base ) - flipt := flipt if config.auth.enabled() { - bytes, err := x509.MarshalPKIXPublicKey(priv.Public()) - if err != nil { - return err - } - - bytes = pem.EncodeToMemory(&pem.Block{ - Type: "public key", - Bytes: bytes, - }) - flipt = flipt. - WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_ENABLED", "true"). - WithNewFile("/etc/flipt/jwt.pem", dagger.ContainerWithNewFileOpts{Contents: string(bytes)}). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_PUBLIC_KEY_FILE", "/etc/flipt/jwt.pem"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_VALIDATE_CLAIMS_ISSUER", "https://flipt.io") + WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true") + + switch config.auth { + case staticAuth, staticAuthNamespaced: + flipt = flipt. + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken) + case k8sAuth: + flipt = flipt. + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_KUBERNETES_ENABLED", "true") + + var saToken string + // run an OIDC server which exposes a JWKS url using a private key we own + // and generate a JWT to act as our SA token + flipt, saToken, err = serveOIDC(ctx, client, base, flipt) + if err != nil { + return err + } + + // mount service account token into base on expected k8s sa token path + base = base.WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/token", dagger.ContainerWithNewFileOpts{ + Contents: saToken, + }) + case jwtAuth: + bytes, err := x509.MarshalPKIXPublicKey(priv.Public()) + if err != nil { + return err + } + + bytes = pem.EncodeToMemory(&pem.Block{ + Type: "public key", + Bytes: bytes, + }) + + flipt = flipt. + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_ENABLED", "true"). + WithNewFile("/etc/flipt/jwt.pem", dagger.ContainerWithNewFileOpts{Contents: string(bytes)}). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_PUBLIC_KEY_FILE", "/etc/flipt/jwt.pem"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_VALIDATE_CLAIMS_ISSUER", "https://flipt.io") + } } name := strings.ToLower(replacer.Replace(fmt.Sprintf("flipt-test-%s-config-%s", caseName, config.name))) @@ -697,3 +729,117 @@ func signJWT(key crypto.PrivateKey, claims interface{}) string { return raw } + +func serveOIDC(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container) (*dagger.Container, string, error) { + priv, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, "", err + } + + rsaSigningKey := &bytes.Buffer{} + if err := pem.Encode(rsaSigningKey, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(priv), + }); err != nil { + return nil, "", err + } + + fliptSAToken := signJWT(priv, map[string]any{ + "exp": time.Now().Add(24 * time.Hour).Unix(), + "iss": "https://discover.srv", + "kubernetes.io": map[string]any{ + "namespace": "flipt", + "pod": map[string]any{ + "name": "flipt-7d26f049-kdurb", + "uid": "bd8299f9-c50f-4b76-af33-9d8e3ef2b850", + }, + "serviceaccount": map[string]any{ + "name": "flipt", + "uid": "4f18914e-f276-44b2-aebd-27db1d8f8def", + }, + }, + }) + + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"Flipt, INC."}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"North Carolina"}, + StreetAddress: []string{""}, + PostalCode: []string{""}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + DNSNames: []string{"discover.svc"}, + } + + caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, "", err + } + + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, "", err + } + + var caCert bytes.Buffer + if err := pem.Encode(&caCert, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }); err != nil { + return nil, "", err + } + + var caPrivKeyPEM bytes.Buffer + pem.Encode(&caPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), + }) + + return flipt. + WithEnvVariable("FLIPT_LOG_LEVEL", "debug"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_KUBERNETES_DISCOVERY_URL", "https://discover.svc"). + WithServiceBinding("discover.svc", base. + WithNewFile("/server.crt", dagger.ContainerWithNewFileOpts{ + Contents: caCert.String(), + }). + WithNewFile("/server.key", dagger.ContainerWithNewFileOpts{ + Contents: caPrivKeyPEM.String(), + }). + WithNewFile("/priv.pem", dagger.ContainerWithNewFileOpts{ + Contents: rsaSigningKey.String(), + }). + WithExposedPort(443). + WithExec([]string{ + "sh", + "-c", + "go run ./build/internal/cmd/discover/... --private-key /priv.pem", + }). + AsService()). + WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/token", + dagger.ContainerWithNewFileOpts{Contents: fliptSAToken}). + WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", + dagger.ContainerWithNewFileOpts{Contents: caCert.String()}), + signJWT(priv, map[string]any{ + "exp": time.Now().Add(24 * time.Hour).Unix(), + "iss": "https://discover.svc", + "kubernetes.io": map[string]any{ + "namespace": "integration", + "pod": map[string]any{ + "name": "integration-test-7d26f049-kdurb", + "uid": "bd8299f9-c50f-4b76-af33-9d8e3ef2b850", + }, + "serviceaccount": map[string]any{ + "name": "integration-test", + "uid": "4f18914e-f276-44b2-aebd-27db1d8f8def", + }, + }, + }), nil +} diff --git a/build/testing/integration/api/api.go b/build/testing/integration/api/api.go index 59bb91cb26..81beb6590b 100644 --- a/build/testing/integration/api/api.go +++ b/build/testing/integration/api/api.go @@ -1392,12 +1392,12 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, opts integration.Tes t.Run("Auth", func(t *testing.T) { t.Run("Self", func(t *testing.T) { _, err := client.Auth().AuthenticationService().GetAuthenticationSelf(ctx) - if authConfig == integration.StaticTokenAuth || authConfig == integration.JWTAuth { - // only valid with a non-scoped token - assert.NoError(t, err) - } else { + if !authConfig.Required() || authConfig.NamespaceScoped() { assert.EqualError(t, err, "rpc error: code = Unauthenticated desc = request was not authenticated") + return } + + assert.NoError(t, err) }) t.Run("Public", func(t *testing.T) { _, err := client.Auth().PublicAuthenticationService().ListAuthenticationMethods(ctx) diff --git a/build/testing/integration/integration.go b/build/testing/integration/integration.go index 21a34aa37f..ef76586b98 100644 --- a/build/testing/integration/integration.go +++ b/build/testing/integration/integration.go @@ -18,7 +18,7 @@ import ( var ( fliptAddr = flag.String("flipt-addr", "grpc://localhost:9000", "Address for running Flipt instance (gRPC only)") - fliptTokenType = flag.String("flipt-token-type", "static", "Type of token to be used during test suite (static, jwt)") + fliptTokenType = flag.String("flipt-token-type", "static", "Type of token to be used during test suite (static, jwt, k8s)") fliptToken = flag.String("flipt-token", "", "Authentication token to be used during test suite") fliptCreateNamespacedToken = flag.Bool("flipt-create-namespaced-token", false, "Create a namespaced token for the test suite") fliptNamespace = flag.String("flipt-namespace", "", "Namespace used to scope API calls.") @@ -32,6 +32,7 @@ const ( StaticTokenAuth StaticTokenAuthNamespaced JWTAuth + K8sAuth ) func (a AuthConfig) String() string { @@ -44,6 +45,8 @@ func (a AuthConfig) String() string { return "StaticTokenAuthNamespaced" case JWTAuth: return "JWTAuth" + case K8sAuth: + return "K8sAuth" default: return "Unknown" } @@ -57,6 +60,10 @@ func (a AuthConfig) Required() bool { return a != NoAuth } +func (a AuthConfig) NamespaceScoped() bool { + return a == StaticTokenAuthNamespaced +} + type TestOpts struct { Addr string Protocol string @@ -92,7 +99,8 @@ func Harness(t *testing.T, fn func(t *testing.T, sdk sdk.SDK, opts TestOpts)) { namespace = *fliptNamespace } - if *fliptTokenType == "static" { + switch *fliptTokenType { + case "static": if *fliptToken != "" { authConfig = StaticTokenAuth @@ -124,13 +132,18 @@ func Harness(t *testing.T, fn func(t *testing.T, sdk sdk.SDK, opts TestOpts)) { )) } } - } else if *fliptTokenType == "jwt" { + case "jwt": if authentication := *fliptToken != ""; authentication { authConfig = JWTAuth opts = append(opts, sdk.WithAuthenticationProvider( sdk.JWTAuthenticationProvider(*fliptToken), )) } + case "k8s": + authConfig = K8sAuth + opts = append(opts, sdk.WithAuthenticationProvider( + sdk.NewKuberntesAuthenticationProvider(transport), + )) } client = sdk.New(transport, opts...) diff --git a/build/testing/integration/readonly/readonly_test.go b/build/testing/integration/readonly/readonly_test.go index a3516192a8..c119494966 100644 --- a/build/testing/integration/readonly/readonly_test.go +++ b/build/testing/integration/readonly/readonly_test.go @@ -661,11 +661,11 @@ func TestReadOnly(t *testing.T) { t.Run("Auth", func(t *testing.T) { t.Run("Self", func(t *testing.T) { _, err := sdk.Auth().AuthenticationService().GetAuthenticationSelf(ctx) - if authConfig == integration.StaticTokenAuth || authConfig == integration.JWTAuth { - assert.NoError(t, err) - } else { + if !authConfig.Required() || authConfig.NamespaceScoped() { assert.EqualError(t, err, "rpc error: code = Unauthenticated desc = request was not authenticated") + return } + assert.NoError(t, err) }) t.Run("Public", func(t *testing.T) { _, err := sdk.Auth().PublicAuthenticationService().ListAuthenticationMethods(ctx) From 71e3d3f2cfd41529cfc0c36329b8a116b5dd7735 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 10:17:57 +0000 Subject: [PATCH 06/14] fix(protoc-gen-go-flipt): correct typo in auth provider constructor name --- internal/cmd/protoc-gen-go-flipt-sdk/main.go | 4 ++-- sdk/go/sdk.gen.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cmd/protoc-gen-go-flipt-sdk/main.go b/internal/cmd/protoc-gen-go-flipt-sdk/main.go index 1fceef8f91..fb35d9b540 100644 --- a/internal/cmd/protoc-gen-go-flipt-sdk/main.go +++ b/internal/cmd/protoc-gen-go-flipt-sdk/main.go @@ -353,9 +353,9 @@ func WithKubernetesExpiryLeeway(d time.Duration) KubernetesAuthenticationProvide } } -// NewKuberntesAuthenticationProvider constructs and configures a new KubernetesAuthenticationProvider +// NewKubernetesAuthenticationProvider constructs and configures a new KubernetesAuthenticationProvider // using the provided transport. -func NewKuberntesAuthenticationProvider(transport Transport, opts ...KubernetesAuthenticationProviderOption) *KubernetesAuthenticationProvider { +func NewKubernetesAuthenticationProvider(transport Transport, opts ...KubernetesAuthenticationProviderOption) *KubernetesAuthenticationProvider { k := &KubernetesAuthenticationProvider{ transport: transport, serviceAccountTokenPath: defaultServiceAccountTokenPath, diff --git a/sdk/go/sdk.gen.go b/sdk/go/sdk.gen.go index a6996c628a..e14e2597f0 100644 --- a/sdk/go/sdk.gen.go +++ b/sdk/go/sdk.gen.go @@ -154,9 +154,9 @@ func WithKubernetesExpiryLeeway(d time.Duration) KubernetesAuthenticationProvide } } -// NewKuberntesAuthenticationProvider constructs and configures a new KubernetesAuthenticationProvider +// NewKubernetesAuthenticationProvider constructs and configures a new KubernetesAuthenticationProvider // using the provided transport. -func NewKuberntesAuthenticationProvider(transport Transport, opts ...KubernetesAuthenticationProviderOption) *KubernetesAuthenticationProvider { +func NewKubernetesAuthenticationProvider(transport Transport, opts ...KubernetesAuthenticationProviderOption) *KubernetesAuthenticationProvider { k := &KubernetesAuthenticationProvider{ transport: transport, serviceAccountTokenPath: defaultServiceAccountTokenPath, From 59d02288615eaab4900c29d6e28cbc5e036be00f Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 10:19:59 +0000 Subject: [PATCH 07/14] chore(sdk/go): update docs for auth providers to link to associated methods --- sdk/go/doc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/go/doc.go b/sdk/go/doc.go index bd42b444b6..4da4cd3387 100644 --- a/sdk/go/doc.go +++ b/sdk/go/doc.go @@ -37,7 +37,7 @@ Note that each of these methods will only work if the target Flipt server instan Currently, there are three implementations: -- [StaticTokenAuthenticationProvider]: +- [StaticTokenAuthenticationProvider](https://www.flipt.io/docs/authentication/methods#static-token): This provider sets a static Flipt client token via the Authentication header with the Bearer scheme. @@ -46,7 +46,7 @@ This provider sets a static Flipt client token via the Authentication header wit client := sdk.New(transport, sdk.WithAuthenticationProvider(provider)) } -- [JWTAuthenticationProvider]: +- [JWTAuthenticationProvider](https://www.flipt.io/docs/authentication/methods#json-web-tokens): This provider sets a pre-generated JSON web-token via the Authentication header with the JWT scheme. @@ -55,7 +55,7 @@ This provider sets a pre-generated JSON web-token via the Authentication header client := sdk.New(transport, sdk.WithAuthenticationProvider(provider)) } -- [KubernetesAuthenticationProvider]: +- [KubernetesAuthenticationProvider](https://www.flipt.io/docs/authentication/methods#kubernetes): This automatically uses the service account token on the host and exchanges it with Flipt for a Flipt client token credential. The credential is then used to authenticate requests, again via the Authentication header and the Bearer scheme. It ensures that the client token is not-expired and requests fresh tokens automatically without intervention. Use this method to automatically authenticate your application with a Flipt deployed into the same Kubernetes cluster. From 5687d104af9f43e933662bd13bc4f641834aa81b Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 10:32:48 +0000 Subject: [PATCH 08/14] fix(test/integration): use corrected method name --- build/testing/integration/integration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/testing/integration/integration.go b/build/testing/integration/integration.go index ef76586b98..33bcde2aa7 100644 --- a/build/testing/integration/integration.go +++ b/build/testing/integration/integration.go @@ -142,7 +142,7 @@ func Harness(t *testing.T, fn func(t *testing.T, sdk sdk.SDK, opts TestOpts)) { case "k8s": authConfig = K8sAuth opts = append(opts, sdk.WithAuthenticationProvider( - sdk.NewKuberntesAuthenticationProvider(transport), + sdk.NewKubernetesAuthenticationProvider(transport), )) } From 9802b54f59ed09a1facc1dc2326841a0241dbbc8 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 11:32:27 +0000 Subject: [PATCH 09/14] fix(github): move env var creation to before tests run --- .github/workflows/integration-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index a9c255ff7f..032e59846f 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -70,6 +70,8 @@ jobs: check-latest: true cache: true + - run: echo "INTEGRATION_TEST_NAME=${{ matrix.test }}" | tr '/' '-' >> $GITHUB_ENV + - name: Install Dagger run: | cd /usr/local @@ -83,8 +85,6 @@ jobs: - name: Run Integration Tests run: mage dagger:run "test:integration ${{ matrix.test }}" - - run: echo "INTEGRATION_TEST_NAME=${{ matrix.test }}" | tr '/' '-' >> $GITHUB_ENV - - name: Upload Flipt Service Logs uses: actions/upload-artifact@v4 if: ${{ always() }} From 9a0b0799a7272476da57fa43f9bca30a1663aaae Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 11:32:52 +0000 Subject: [PATCH 10/14] fix(test/integration): ensure token service always enabled --- build/testing/integration.go | 7 +++++-- build/testing/integration/api/authenticated.go | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/build/testing/integration.go b/build/testing/integration.go index 730acd0a92..955e161d0e 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -182,12 +182,12 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger if config.auth.enabled() { flipt = flipt. - WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true") + WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true") switch config.auth { case staticAuth, staticAuthNamespaced: flipt = flipt. - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true"). WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken) case k8sAuth: flipt = flipt. @@ -428,6 +428,7 @@ func git(ctx context.Context, client *dagger.Client, base, flipt *dagger.Contain func s3(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error { minio := client.Container(). From("quay.io/minio/minio:latest"). + WithEnvVariable("UNIQUE", uuid.New().String()). WithExposedPort(9009). WithEnvVariable("MINIO_ROOT_USER", "user"). WithEnvVariable("MINIO_ROOT_PASSWORD", "password"). @@ -657,6 +658,7 @@ func suite(ctx context.Context, dir string, base, flipt *dagger.Container, conf func azblob(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error { azurite := client.Container(). From("mcr.microsoft.com/azure-storage/azurite"). + WithEnvVariable("UNIQUE", uuid.New().String()). WithExposedPort(10000). WithExec([]string{"azurite-blob", "--blobHost", "0.0.0.0", "--silent"}). AsService() @@ -689,6 +691,7 @@ func azblob(ctx context.Context, client *dagger.Client, base, flipt *dagger.Cont func gcs(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error { gcs := client.Container(). From("fsouza/fake-gcs-server"). + WithEnvVariable("UNIQUE", uuid.New().String()). WithExposedPort(4443). WithExec([]string{"-scheme", "http", "-public-host", "gcs:4443"}). AsService() diff --git a/build/testing/integration/api/authenticated.go b/build/testing/integration/api/authenticated.go index 3728deff6d..120842a477 100644 --- a/build/testing/integration/api/authenticated.go +++ b/build/testing/integration/api/authenticated.go @@ -35,7 +35,7 @@ func Authenticated(t *testing.T, client sdk.SDK, opts integration.TestOpts) { authn, err := client.Auth().AuthenticationService().GetAuthenticationSelf(ctx) - if opts.AuthConfig == integration.StaticTokenAuthNamespaced { + if opts.AuthConfig.NamespaceScoped() { require.EqualError(t, err, "rpc error: code = Unauthenticated desc = request was not authenticated") return } @@ -55,7 +55,7 @@ func Authenticated(t *testing.T, client sdk.SDK, opts integration.TestOpts) { }) // a namespaced token should not be able to create any other tokens - if opts.AuthConfig == integration.StaticTokenAuthNamespaced { + if opts.AuthConfig.NamespaceScoped() { require.EqualError(t, err, "rpc error: code = Unauthenticated desc = request was not authenticated") return } @@ -82,7 +82,7 @@ func Authenticated(t *testing.T, client sdk.SDK, opts integration.TestOpts) { }) // a namespaced token should not be able to create any other tokens - if opts.AuthConfig == integration.StaticTokenAuthNamespaced { + if opts.AuthConfig.NamespaceScoped() { require.EqualError(t, err, "rpc error: code = Unauthenticated desc = request was not authenticated") return } @@ -105,7 +105,7 @@ func Authenticated(t *testing.T, client sdk.SDK, opts integration.TestOpts) { ExpiresAt: flipt.Now(), }) - if opts.AuthConfig == integration.StaticTokenAuthNamespaced { + if opts.AuthConfig.NamespaceScoped() { require.EqualError(t, err, "rpc error: code = Unauthenticated desc = request was not authenticated") return } From 47faaaa86372ec07d5a48697a679d0faba87a6fa Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 11:39:27 +0000 Subject: [PATCH 11/14] fix(test/integration): ensure token service always bootstrapped consistently --- build/testing/integration.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/build/testing/integration.go b/build/testing/integration.go index 955e161d0e..1d4de8ff42 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -183,12 +183,10 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger if config.auth.enabled() { flipt = flipt. WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true") + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken) switch config.auth { - case staticAuth, staticAuthNamespaced: - flipt = flipt. - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken) case k8sAuth: flipt = flipt. WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_KUBERNETES_ENABLED", "true") From 4b38fdb82a392d766885a2188d33d3f15ce3f784 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 13:35:10 +0000 Subject: [PATCH 12/14] chore(dagger): upgrade from 0.9.4 to 0.9.5 --- .github/workflows/integration-test.yml | 2 +- .github/workflows/test.yml | 2 +- build/testing/integration.go | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 032e59846f..9f7ea71420 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -8,7 +8,7 @@ on: env: GO_VERSION: "1.21" - DAGGER_VERSION: "0.9.4" + DAGGER_VERSION: "0.9.5" jobs: cli: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80162eda3f..507c559fbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ on: env: GO_VERSION: "1.21" - DAGGER_VERSION: "0.9.4" + DAGGER_VERSION: "0.9.5" jobs: test: diff --git a/build/testing/integration.go b/build/testing/integration.go index 1d4de8ff42..d75a3a34bf 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -431,10 +431,11 @@ func s3(ctx context.Context, client *dagger.Client, base, flipt *dagger.Containe WithEnvVariable("MINIO_ROOT_USER", "user"). WithEnvVariable("MINIO_ROOT_PASSWORD", "password"). WithEnvVariable("MINIO_BROWSER", "off"). - WithExec([]string{"server", "/data", "--address", ":9009", "--quiet"}) + WithExec([]string{"server", "/data", "--address", ":9009", "--quiet"}). + AsService() _, err := base. - WithServiceBinding("minio", minio.AsService()). + WithServiceBinding("minio", minio). WithEnvVariable("AWS_ACCESS_KEY_ID", "user"). WithEnvVariable("AWS_SECRET_ACCESS_KEY", "password"). WithExec([]string{"go", "run", "./build/internal/cmd/minio/...", "-minio-url", "http://minio:9009", "-testdata-dir", singleRevisionTestdataDir}). @@ -444,7 +445,7 @@ func s3(ctx context.Context, client *dagger.Client, base, flipt *dagger.Containe } flipt = flipt. - WithServiceBinding("minio", minio.AsService()). + WithServiceBinding("minio", minio). WithEnvVariable("FLIPT_LOG_LEVEL", "DEBUG"). WithEnvVariable("AWS_ACCESS_KEY_ID", "user"). WithEnvVariable("AWS_SECRET_ACCESS_KEY", "password"). From 4cc378364f424c0e3bda86c7341567aa699a32cb Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 13:39:40 +0000 Subject: [PATCH 13/14] chore(test/integration): update documentation around serveOIDC --- build/testing/integration.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build/testing/integration.go b/build/testing/integration.go index d75a3a34bf..dc873bf96e 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -732,6 +732,13 @@ func signJWT(key crypto.PrivateKey, claims interface{}) string { return raw } +// serveOIDC runs a mini OIDC-style key provider and mounts it as a service onto Flipt. +// This provider is designed to mimic how kubernetes exposes JWKS endpoints for its service account tokens. +// The function creates signing keys and TLS CA certificates which is shares with the provider and +// with Flipt itself. This is to facilitate Flipt using the custom CA to authenticate the provider. +// The function generates two JWTs, one for Flipt to identify itself and one which is returned to the caller. +// The caller can use this as the service account token identity to be mounted into the container with the +// client used for running the test and authenticating with Flipt. func serveOIDC(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container) (*dagger.Container, string, error) { priv, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { @@ -746,6 +753,7 @@ func serveOIDC(ctx context.Context, client *dagger.Client, base, flipt *dagger.C return nil, "", err } + // generate a SA style JWT for identifying the Flipt service fliptSAToken := signJWT(priv, map[string]any{ "exp": time.Now().Add(24 * time.Hour).Unix(), "iss": "https://discover.srv", @@ -762,6 +770,7 @@ func serveOIDC(ctx context.Context, client *dagger.Client, base, flipt *dagger.C }, }) + // generate a CA certificate to share between Flipt and the mini OIDC server ca := &x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ @@ -829,6 +838,8 @@ func serveOIDC(ctx context.Context, client *dagger.Client, base, flipt *dagger.C dagger.ContainerWithNewFileOpts{Contents: fliptSAToken}). WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", dagger.ContainerWithNewFileOpts{Contents: caCert.String()}), + // generate a JWT to used to identify the workload communicating with Flipt + // using the private key components of the key pair served by the OIDC server signJWT(priv, map[string]any{ "exp": time.Now().Add(24 * time.Hour).Unix(), "iss": "https://discover.svc", From ce529feca2a1ece71cf3d93dc0005492ec48b91e Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Wed, 24 Jan 2024 13:56:18 +0000 Subject: [PATCH 14/14] refactor(testing/integration): move case construction into semaphore guarded take --- build/testing/integration.go | 95 ++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/build/testing/integration.go b/build/testing/integration.go index dc873bf96e..6d36998683 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -180,57 +180,58 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger base = base ) - if config.auth.enabled() { - flipt = flipt. - WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken) - - switch config.auth { - case k8sAuth: + g.Go(take(func() error { + if config.auth.enabled() { flipt = flipt. - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_KUBERNETES_ENABLED", "true") - - var saToken string - // run an OIDC server which exposes a JWKS url using a private key we own - // and generate a JWT to act as our SA token - flipt, saToken, err = serveOIDC(ctx, client, base, flipt) - if err != nil { - return err - } - - // mount service account token into base on expected k8s sa token path - base = base.WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/token", dagger.ContainerWithNewFileOpts{ - Contents: saToken, - }) - case jwtAuth: - bytes, err := x509.MarshalPKIXPublicKey(priv.Public()) - if err != nil { - return err + WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken) + + switch config.auth { + case k8sAuth: + flipt = flipt. + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_KUBERNETES_ENABLED", "true") + + var saToken string + // run an OIDC server which exposes a JWKS url using a private key we own + // and generate a JWT to act as our SA token + flipt, saToken, err = serveOIDC(ctx, client, base, flipt) + if err != nil { + return err + } + + // mount service account token into base on expected k8s sa token path + base = base.WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/token", dagger.ContainerWithNewFileOpts{ + Contents: saToken, + }) + case jwtAuth: + bytes, err := x509.MarshalPKIXPublicKey(priv.Public()) + if err != nil { + return err + } + + bytes = pem.EncodeToMemory(&pem.Block{ + Type: "public key", + Bytes: bytes, + }) + + flipt = flipt. + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_ENABLED", "true"). + WithNewFile("/etc/flipt/jwt.pem", dagger.ContainerWithNewFileOpts{Contents: string(bytes)}). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_PUBLIC_KEY_FILE", "/etc/flipt/jwt.pem"). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_VALIDATE_CLAIMS_ISSUER", "https://flipt.io") } - - bytes = pem.EncodeToMemory(&pem.Block{ - Type: "public key", - Bytes: bytes, - }) - - flipt = flipt. - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_ENABLED", "true"). - WithNewFile("/etc/flipt/jwt.pem", dagger.ContainerWithNewFileOpts{Contents: string(bytes)}). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_PUBLIC_KEY_FILE", "/etc/flipt/jwt.pem"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_JWT_VALIDATE_CLAIMS_ISSUER", "https://flipt.io") } - } - - name := strings.ToLower(replacer.Replace(fmt.Sprintf("flipt-test-%s-config-%s", caseName, config.name))) - flipt = flipt. - WithEnvVariable("CI", os.Getenv("CI")). - WithEnvVariable("FLIPT_LOG_LEVEL", "debug"). - WithEnvVariable("FLIPT_LOG_FILE", fmt.Sprintf("/var/opt/flipt/logs/%s.log", name)). - WithMountedCache("/var/opt/flipt/logs", logs). - WithExposedPort(config.port) - g.Go(take(fn(ctx, client, base.Pipeline(name), flipt, config))) + name := strings.ToLower(replacer.Replace(fmt.Sprintf("flipt-test-%s-config-%s", caseName, config.name))) + flipt = flipt. + WithEnvVariable("CI", os.Getenv("CI")). + WithEnvVariable("FLIPT_LOG_LEVEL", "debug"). + WithEnvVariable("FLIPT_LOG_FILE", fmt.Sprintf("/var/opt/flipt/logs/%s.log", name)). + WithMountedCache("/var/opt/flipt/logs", logs). + WithExposedPort(config.port) + return fn(ctx, client, base.Pipeline(name), flipt, config)() + })) } }