Skip to content

Commit 1599ece

Browse files
committed
feat: Support JWT Assertion Grant Floew
With the release of UAA 76.23.0, Cloud Foundry support JWT Assertion Grant Flow Refer: - [What is JWT Bearer flow](https://docs.secureauth.com/ciam/en/using-jwt-profile-for-oauth-2-0-authorization-flows.html) - [UAA](https://docs.cloudfoundry.org/api/uaa/version/77.34.0/index.html#jwt-bearer-token-grant) - [RFC-7523](https://datatracker.ietf.org/doc/html/rfc7523) In this PR: - Basic code refactoring - Add `assertion_token` field for the provider - Add case for creating session with assertion token - Add example in Authentication Docs - Update go mod to the latest version of the go-cfclient fork Replace this in future to the main go-cfclient library when [fork not needed](cloudfoundry/go-cfclient#477)
1 parent 44cdbfb commit 1599ece

File tree

9 files changed

+727
-47
lines changed

9 files changed

+727
-47
lines changed

Authentication.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ provider "cloudfoundry" {
3030
}
3131
```
3232

33+
## OAuth JWT Assertion Bearer Flow
34+
35+
Use the env variable `CF_ASSERTION_TOKEN`. Typically for cloudfoundry, this login also would use a custom `origin` (unless the default `origin` is configured to support this method)
36+
37+
Refer [this document](https://docs.secureauth.com/ciam/en/using-jwt-profile-for-oauth-2-0-authorization-flows.html) to understand the JWT Assertion Bearer Flow.
38+
39+
This flow can be used in automated scenarios where the OIDC provider that is trusted by UAA has a secure means of providing assertion tokens. These tokens are short lived.
40+
41+
A typical example would be using the the Open-ID Connect feature of [github](https://docs.github.com/en/actions/concepts/security/openid-connect)
42+
In this scenario, an `origin` in UAA would be configured to use Github OIDC as an [identity provider](https://docs.cloudfoundry.org/uaa/identity-providers.html#oidc). Refer this [blog](https://community.sap.com/t5/technology-blog-posts-by-sap/authenticating-github-actions-workflows-deploying-to-the-sap-btp-cloud/ba-p/14075047) where a similar setup is done with the cf cli. **Similarly**, the terraform provider can then use assertion tokens provided by github in a github action to login to Cloud Foundry with that specific `origin`
43+
44+
```hcl
45+
provider "cloudfoundry" {
46+
api_url = "<CF-API-URL>"
47+
cf_client_id = "<CF-ASSERTION-TOKEN>"
48+
origin = "<CF-ORIGIN>"
49+
}
50+
```
51+
3352
## Using cf-cli configuration.
3453

3554
If you have installed the [cf-cli](https://docs.cloudfoundry.org/cf-cli/) and have [logged into the environment](https://docs.cloudfoundry.org/cf-cli/getting-started.html#login), the the cloudfoundry terraform provider can use the default configuration of the cf-cli (present in `~/.cf` folder) to connect to the environment.

cloudfoundry/provider/fixtures/provider.assertion_token.yaml

Lines changed: 600 additions & 0 deletions
Large diffs are not rendered by default.

cloudfoundry/provider/managers/session.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type CloudFoundryProviderConfig struct {
2323
Origin string
2424
AccessToken string
2525
RefreshToken string
26+
AssertionToken string
2627
}
2728

2829
type Session struct {
@@ -52,19 +53,22 @@ func (c *CloudFoundryProviderConfig) NewSession(httpClient *http.Client, req pro
5253
if c.SkipSslValidation {
5354
opts = append(opts, config.SkipTLSValidation())
5455
}
56+
if c.Origin != "" {
57+
opts = append(opts, config.Origin(c.Origin))
58+
}
5559
switch {
5660
case c.User != "" && c.Password != "":
5761
opts = append(opts, config.UserPassword(c.User, c.Password))
58-
if c.Origin != "" {
59-
opts = append(opts, config.Origin(c.Origin))
60-
}
6162
cfg, err = config.New(c.Endpoint, opts...)
6263
case c.CFClientID != "" && c.CFClientSecret != "":
6364
opts = append(opts, config.ClientCredentials(c.CFClientID, c.CFClientSecret))
6465
cfg, err = config.New(c.Endpoint, opts...)
6566
case c.AccessToken != "":
6667
opts = append(opts, config.Token(c.AccessToken, c.RefreshToken))
6768
cfg, err = config.New(c.Endpoint, opts...)
69+
case c.AssertionToken != "":
70+
opts = append(opts, config.JWTBearerAssertion(c.AssertionToken))
71+
cfg, err = config.New(c.Endpoint, opts...)
6872
default:
6973
cfg, err = config.NewFromCFHome(opts...)
7074
}

cloudfoundry/provider/provider.go

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type CloudFoundryProviderModel struct {
3737
Origin types.String `tfsdk:"origin"`
3838
AccessToken types.String `tfsdk:"access_token"`
3939
RefreshToken types.String `tfsdk:"refresh_token"`
40+
AssertionToken types.String `tfsdk:"assertion_token"`
4041
}
4142

4243
func (p *CloudFoundryProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
@@ -112,6 +113,14 @@ func (p *CloudFoundryProvider) Schema(ctx context.Context, req provider.SchemaRe
112113
stringvalidator.LengthAtLeast(1),
113114
},
114115
},
116+
"assertion_token": schema.StringAttribute{
117+
MarkdownDescription: "OAuth JWT assertion token. Used for OAuth 2.0 JWT Bearer Assertion Grant flow to authenticate with Cloud Foundry. Typically used with a custom origin.",
118+
Optional: true,
119+
Sensitive: true,
120+
Validators: []validator.String{
121+
stringvalidator.LengthAtLeast(1),
122+
},
123+
},
115124
},
116125
}
117126
}
@@ -134,7 +143,7 @@ func addTypeCastAttributeError(resp *provider.ConfigureResponse, expectedType st
134143
func checkConfigUnknown(config *CloudFoundryProviderModel, resp *provider.ConfigureResponse) {
135144
_, cfconfigerr := cfconfig.NewFromCFHome()
136145

137-
anyParamExists := !config.User.IsUnknown() || !config.Password.IsUnknown() || !config.CFClientID.IsUnknown() || !config.CFClientSecret.IsUnknown() || !config.AccessToken.IsUnknown()
146+
anyParamExists := !config.User.IsUnknown() || !config.Password.IsUnknown() || !config.CFClientID.IsUnknown() || !config.CFClientSecret.IsUnknown() || !config.AccessToken.IsUnknown() || !config.RefreshToken.IsUnknown() || !config.AssertionToken.IsUnknown()
138147

139148
/*
140149
There can be 3 cases of error:
@@ -162,27 +171,30 @@ func checkConfigUnknown(config *CloudFoundryProviderModel, resp *provider.Config
162171
}
163172
}
164173

165-
func checkConfig(resp *provider.ConfigureResponse, endpoint string, user string, password string, cfclientid string, cfclientsecret string, accesstoken string) {
174+
func checkConfig(resp *provider.ConfigureResponse, config managers.CloudFoundryProviderConfig) {
166175
_, cfconfigerr := cfconfig.NewFromCFHome()
167176

168-
anyParamExists := user != "" || password != "" || cfclientid != "" || cfclientsecret != "" || accesstoken != ""
169-
170-
if (endpoint == "" && anyParamExists) || (endpoint != "" && !anyParamExists) || (!anyParamExists && cfconfigerr != nil) {
177+
anyParamExists := config.User != "" || config.Password != "" || config.CFClientID != "" || config.CFClientSecret != "" || config.AccessToken != "" || config.RefreshToken != "" || config.AssertionToken != ""
178+
// There can be 3 cases of error:
179+
// 1. If endpoint is empty and any other parameter is set
180+
// 2. If endpoint is set and all other parameter is empty
181+
// 3. If all parameters are empty and CF config is not correctly set
182+
if (config.Endpoint == "" && anyParamExists) || (config.Endpoint != "" && !anyParamExists) || (!anyParamExists && cfconfigerr != nil) {
171183
resp.Diagnostics.AddError(
172184
"Unable to create CF Client due to missing values",
173185
"Either user/password or client_id/client_secret or access_token must be set with api_url or CF config must exist in path (default ~/.cf/config.json)",
174186
)
175187
}
176188

177-
if endpoint != "" {
189+
if config.Endpoint != "" {
178190
switch {
179-
case user == "" && password != "":
191+
case config.User == "" && config.Password != "":
180192
addGenericAttributeError(resp, "Missing", "user", "Username", "CF_USER")
181-
case user != "" && password == "":
193+
case config.User != "" && config.Password == "":
182194
addGenericAttributeError(resp, "Missing", "password", "Password", "CF_PASSWORD")
183-
case cfclientid == "" && cfclientsecret != "":
195+
case config.CFClientID == "" && config.CFClientSecret != "":
184196
addGenericAttributeError(resp, "Missing", "cf_client_id", "Client ID", "CF_CLIENT_ID")
185-
case cfclientid != "" && cfclientsecret == "":
197+
case config.CFClientID != "" && config.CFClientSecret == "":
186198
addGenericAttributeError(resp, "Missing", "cf_client_secret", " Client Secret", "CF_CLIENT_SECRET")
187199
}
188200
}
@@ -192,67 +204,63 @@ func getAndSetProviderValues(config *CloudFoundryProviderModel, resp *provider.C
192204
// Default values to environment variables, but override
193205
// with Terraform configuration value if set.
194206

195-
endpoint := os.Getenv("CF_API_URL")
196-
user := os.Getenv("CF_USER")
197-
password := os.Getenv("CF_PASSWORD")
198-
origin := os.Getenv("CF_ORIGIN")
199-
cfclientid := os.Getenv("CF_CLIENT_ID")
200-
cfclientsecret := os.Getenv("CF_CLIENT_SECRET")
201-
cfaccesstoken := os.Getenv("CF_ACCESS_TOKEN")
202-
cfrefreshtoken := os.Getenv("CF_REFRESH_TOKEN")
207+
c := managers.CloudFoundryProviderConfig{
208+
Endpoint: os.Getenv("CF_API_URL"),
209+
User: os.Getenv("CF_USER"),
210+
Password: os.Getenv("CF_PASSWORD"),
211+
CFClientID: os.Getenv("CF_CLIENT_ID"),
212+
CFClientSecret: os.Getenv("CF_CLIENT_SECRET"),
213+
Origin: os.Getenv("CF_ORIGIN"),
214+
AccessToken: os.Getenv("CF_ACCESS_TOKEN"),
215+
RefreshToken: os.Getenv("CF_REFRESH_TOKEN"),
216+
AssertionToken: os.Getenv("CF_ASSERTION_TOKEN"),
217+
}
203218

204-
var skipsslvalidation bool
205219
var err error
206220
if os.Getenv("CF_SKIP_SSL_VALIDATION") != "" {
207-
skipsslvalidation, err = strconv.ParseBool(os.Getenv("CF_SKIP_SSL_VALIDATION"))
221+
c.SkipSslValidation, err = strconv.ParseBool(os.Getenv("CF_SKIP_SSL_VALIDATION"))
208222
if err != nil {
209223
addTypeCastAttributeError(resp, "Boolean", "skip_ssl_validation", "Skip SSL Validation", "CF_SKIP_SSL_VALIDATION")
210224
return nil
211225
}
212226
}
213227
if !config.Endpoint.IsNull() {
214-
endpoint = config.Endpoint.ValueString()
228+
c.Endpoint = config.Endpoint.ValueString()
215229
}
216230
if !config.User.IsNull() {
217-
user = config.User.ValueString()
231+
c.User = config.User.ValueString()
218232
}
219233
if !config.Password.IsNull() {
220-
password = config.Password.ValueString()
234+
c.Password = config.Password.ValueString()
221235
}
222236
if !config.CFClientID.IsNull() {
223-
cfclientid = config.CFClientID.ValueString()
237+
c.CFClientID = config.CFClientID.ValueString()
224238
}
225239
if !config.CFClientSecret.IsNull() {
226-
cfclientsecret = config.CFClientSecret.ValueString()
240+
c.CFClientSecret = config.CFClientSecret.ValueString()
227241
}
228242
if !config.Origin.IsNull() {
229-
origin = config.Origin.ValueString()
243+
c.Origin = config.Origin.ValueString()
230244
}
231245
if !config.AccessToken.IsNull() {
232-
cfaccesstoken = config.AccessToken.ValueString()
246+
c.AccessToken = config.AccessToken.ValueString()
233247
}
234248
if !config.RefreshToken.IsNull() {
235-
cfrefreshtoken = config.RefreshToken.ValueString()
249+
c.RefreshToken = config.RefreshToken.ValueString()
250+
}
251+
if !config.AssertionToken.IsNull() {
252+
c.AssertionToken = config.AssertionToken.ValueString()
236253
}
237-
checkConfig(resp, endpoint, user, password, cfclientid, cfclientsecret, cfaccesstoken)
254+
255+
checkConfig(resp, c)
238256
if resp.Diagnostics.HasError() {
239257
return nil
240258
}
241259
if !config.SkipSslValidation.IsNull() {
242-
skipsslvalidation = config.SkipSslValidation.ValueBool()
260+
c.SkipSslValidation = config.SkipSslValidation.ValueBool()
243261
}
262+
c.Endpoint = strings.TrimSuffix(c.Endpoint, "/")
244263

245-
c := managers.CloudFoundryProviderConfig{
246-
Endpoint: strings.TrimSuffix(endpoint, "/"),
247-
User: user,
248-
Password: password,
249-
CFClientID: cfclientid,
250-
CFClientSecret: cfclientsecret,
251-
SkipSslValidation: skipsslvalidation,
252-
Origin: origin,
253-
AccessToken: cfaccesstoken,
254-
RefreshToken: cfrefreshtoken,
255-
}
256264
return &c
257265
}
258266
func (p *CloudFoundryProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {

cloudfoundry/provider/provider_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type CloudFoundryProviderConfigPtr struct {
3030
Origin *string
3131
AccessToken *string
3232
RefreshToken *string
33+
AssertionToken *string
3334
}
3435

3536
var redactedTestUser = CloudFoundryProviderConfigPtr{
@@ -40,6 +41,8 @@ var redactedTestUser = CloudFoundryProviderConfigPtr{
4041
CFClientSecret: strtostrptr("xxxx"),
4142
AccessToken: strtostrptr("bearer eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vdWFhLngueC54LnguY29tL3Rva2VuX2tleXMiLCJraWQiOiJrZXktMSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0YmYyMmRhYjNiYmU0NTg1OTUwM2Y0MWExZmRkZmFmOCIsImNsaWVudF9hdXRoX21ldGhvZCI6Im5vbmUiLCJzdWIiOiIxZjI2M2UwMC05YTA3LTQyZDgtYmU3MS1iMThiZTZkMTBiNDUiLCJzY29wZSI6WyJvcGVuaWQiLCJ1YWEudXNlciIsImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsInBhc3N3b3JkLndyaXRlIiwiY2xvdWRfY29udHJvbGxlci53cml0ZSJdLCJjbGllbnRfaWQiOiJjZiIsImNpZCI6ImNmIiwiYXpwIjoiY2YiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiMWYyNjNlMDAtOWEwNy00MmQ4LWJlNzEtYjE4YmU2ZDEwYjQ1Iiwib3JpZ2luIjoic2FwLmlkcyIsInVzZXJfbmFtZSI6InRlc3RfdXNlckB4LmNvbSIsImVtYWlsIjoidGVzdF91c2VyQHguY29tIiwiYXV0aF90aW1lIjoxNzA1MDYwOTM2LCJyZXZfc2lnIjoiZGUxMDU3ZDEiLCJpYXQiOjE3MDUwNjA5MzYsImV4cCI6MjcwNTA2MjEzNiwiaXNzIjoiaHR0cHM6Ly91YWEueC54LngueC5jb20vb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsiY2xvdWRfY29udHJvbGxlciIsInBhc3N3b3JkIiwiY2YiLCJ1YWEiLCJvcGVuaWQiXX0.DADNqcmHbP8R0Dp3pMZVE7OeD5eTmcwh5dyFKFpryGEl3QqXKd1Af3raTFnJe1SRi66qjkvpdLub31Fh3LDdkAPAoFYshvwxozCdEinGYEx-qlW1Ttt6qyk_0y3CjKDExv43F8CpHwqD41A57IOAbz14revnb6tbW9pA_dBxhF9sYdXJvhPOnGUDKgv5SIYNUyt0_ekEaHNMVHp__4dnaCw7qdMkJ7Y7Pn4ES3KJqc88Ed9PzRJw0WQzwvHlJbQyCtpBXFx_ZzIEFNjcXo9p-YbezEKVypKlREs59h-HzpbhLwjW9_MzuY3wFveYT4FLsF-U0s0KeQq83E8J_zWRhw"),
4243
RefreshToken: strtostrptr("xxxx"),
44+
Origin: strtostrptr("dummy-origin"),
45+
AssertionToken: strtostrptr("eyJhbGciOiJSUzI1NiIsImtpZDI6Im1vY2sta2V5LWlkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJodHRwczovL3RlcnJhZm9ybWVkcy5hY2NvdW50cy5vbmRlbWFuZC5jb20iLCJleHAiOjE3NTI3NTU1NjYsImlhdCI6MTc1Mjc1MTk2NiwiaXNzIjoiaHR0cHM6Ly9tb2NrLW9pZGMtcHJvdmlkZXItc2lsbHktYWxsaWdhdG9yLW9hLmNmYXBwcy51czEwLmhhbmEub25kZW1hbmQuY29tIiwicmVwb3NpdG9yeSI6Im1vY2svcmVwbyIsInN1YiI6Im1vY2svcmVwb0BhLmNvbSJ9.M5fpxHntMa4454z97z5fU0DYbbre02LFCivVShPjwssP2b6if7zMRzpX31OUOW3QdtQO3X2tHZfOx6cF4ya-LrkJRmZrL_OVW2inaY3_o3vYFH50tXXinmy7X4mHPLg93eDZq-sEMNrRWTKomrG_3Nj8wySyHpAUWtGd5bYOf2fD4t6WTHgOgjXBnmREIP_HU95yE3XiP1CggJfb-ll7MApxfivw_sZUbSL_Vd5pvwMcZzTji_mTGoT6zIbYg4ndkk_m_RD9GBz-lqfL6BcGe_fYJAdFtZkZ2ws6utioKFN93mdUXazTeDIyi-G1uL0LcqUVx9wGrjcHUa8GPfh5hA"),
4346
}
4447

4548
func hclProvider(cfConfig *CloudFoundryProviderConfigPtr) string {
@@ -73,6 +76,9 @@ func hclProvider(cfConfig *CloudFoundryProviderConfigPtr) string {
7376
{{if .RefreshToken}}
7477
refresh_token = "{{.RefreshToken}}"
7578
{{- end }}
79+
{{if .AssertionToken}}
80+
assertion_token = "{{.AssertionToken}}"
81+
{{- end }}
7682
}`
7783
tmpl, err := template.New("provider").Parse(s)
7884
if err != nil {
@@ -212,6 +218,38 @@ func TestCloudFoundryProvider_Configure(t *testing.T) {
212218
},
213219
})
214220
})
221+
t.Run("user login with valid assertion token", func(t *testing.T) {
222+
endpoint := strtostrptr(os.Getenv("TEST_CF_API_URL"))
223+
assertionToken := strtostrptr(os.Getenv("TEST_CF_ASSERTION_TOKEN"))
224+
origin := strtostrptr(os.Getenv("TEST_CF_ORIGIN"))
225+
if *endpoint == "" || *assertionToken == "" || *origin == "" {
226+
t.Logf("\nATTENTION: Using redacted user credentials since endpoint, assertion & origin not set as env \n Make sure you are not triggering a recording else test will fail")
227+
endpoint = redactedTestUser.Endpoint
228+
assertionToken = redactedTestUser.AssertionToken
229+
origin = redactedTestUser.Origin
230+
}
231+
cfg := CloudFoundryProviderConfigPtr{
232+
Endpoint: endpoint,
233+
AssertionToken: assertionToken,
234+
Origin: origin,
235+
}
236+
237+
recUserPass := cfg.SetupVCR(t, "fixtures/provider.assertion_token")
238+
defer stopQuietly(recUserPass)
239+
240+
testingResource.Test(t, testingResource.TestCase{
241+
IsUnitTest: true,
242+
ProtoV6ProviderFactories: getProviders(recUserPass.GetDefaultClient()),
243+
Steps: []testingResource.TestStep{
244+
{
245+
Config: hclProvider(&cfg) + `
246+
data "cloudfoundry_org" "org" {
247+
name = "BTP-Terraformers-Prod_mock-github-oidc-test"
248+
}`,
249+
},
250+
},
251+
})
252+
})
215253
t.Run("user login with valid home directory", func(t *testing.T) {
216254
cfg := getCFHomeConf()
217255
recHomeDir := cfg.SetupVCR(t, "fixtures/provider.home_dir")

cloudfoundry/provider/utils_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ func (cfg *CloudFoundryProviderConfigPtr) GetHook() func(i *cassette.Interaction
153153
}
154154
regList = append(regList, reg)
155155
}
156+
if cfg.AssertionToken != nil {
157+
reg := reg{
158+
regexpattern: []*regexp.Regexp{
159+
regexp.MustCompile(*cfg.AssertionToken),
160+
regexp.MustCompile(url.QueryEscape(*cfg.AssertionToken)),
161+
},
162+
redactString: *redactedTestUser.AssertionToken,
163+
}
164+
regList = append(regList, reg)
165+
}
156166
interactionJson, err := json.Marshal(i)
157167
if err != nil {
158168
panic(err)

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ provider "cloudfoundry" {
3333

3434
- `access_token` (String, Sensitive) OAuth token to authenticate with Cloud Foundry
3535
- `api_url` (String) Specific URL representing the entry point for communication between the client and a Cloud Foundry instance.
36+
- `assertion_token` (String, Sensitive) OAuth JWT assertion token. Used for OAuth 2.0 JWT Bearer Assertion Grant flow to authenticate with Cloud Foundry. Typically used with a custom origin.
3637
- `cf_client_id` (String) Unique identifier for a client application used in authentication and authorization processes
3738
- `cf_client_secret` (String, Sensitive) A confidential string used by a client application for secure authentication and authorization, requires cf_client_id to authenticate
3839
- `origin` (String) Indicates the identity provider to be used for login

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ require (
2222
gopkg.in/yaml.v2 v2.4.0
2323
)
2424

25-
replace github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.11.0.20250320145327-6946bc732186 => github.com/ANUGRAHG/go-cfclient/v3 v3.0.0-20250619101525-5bab1c3424d6
25+
replace github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.11.0.20250320145327-6946bc732186 => github.com/ANUGRAHG/go-cfclient/v3 v3.0.0-20250715085712-0c590acfa414
2626

2727
require (
2828
code.cloudfoundry.org/cf-networking-helpers v0.49.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
88
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
99
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
1010
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
11-
github.com/ANUGRAHG/go-cfclient/v3 v3.0.0-20250619101525-5bab1c3424d6 h1:URwb3r4R1Axb6PdxGCozg/IyWBTH86emWWSteIFuRHA=
12-
github.com/ANUGRAHG/go-cfclient/v3 v3.0.0-20250619101525-5bab1c3424d6/go.mod h1:+sY77PKx6xxDyApQ07webuPs80UMefAOTMPFuwXUerM=
11+
github.com/ANUGRAHG/go-cfclient/v3 v3.0.0-20250715085712-0c590acfa414 h1:HKM4l1w496sABMBkAtEqLiB7CBD88sT1N36nFWStjeQ=
12+
github.com/ANUGRAHG/go-cfclient/v3 v3.0.0-20250715085712-0c590acfa414/go.mod h1:+sY77PKx6xxDyApQ07webuPs80UMefAOTMPFuwXUerM=
1313
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
1414
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
1515
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=

0 commit comments

Comments
 (0)