Skip to content

Commit 5236e15

Browse files
jdortizgotsysdba
andauthored
Address issue #4 (lock concurrent instances) and #5 (verify token and retry) (#7)
* Add token verification and retries * verifyToken * VERIFY_TOKEN env * Fix YAML * Remove binary; .gitignore * Simplify obtaining verifyToken from the environment * switch to false * Change Env * string * Removed * Tested working * Closes #4 * Update run-locally example * Move defer to right after resource allocation * Add config method for using token validation setting * accept StatusOK as valid * Add test to image URL parsing --------- Co-authored-by: gotsysdba <[email protected]>
1 parent 3496d40 commit 5236e15

File tree

12 files changed

+252
-39
lines changed

12 files changed

+252
-39
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Created by https://www.toptal.com/developers/gitignore/api/macos,go,visualstudiocode
22
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,go,visualstudiocode
33

4+
### Binary ###
5+
oke-credential-provider-for-ocir-linux-amd64
6+
47
### Go ###
58
# If you prefer the allow list template instead of the deny list, see community template:
69
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore

cmd/provider.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,35 @@ package main
77

88
import (
99
"flag"
10+
"os"
11+
"time"
1012

1113
"github.com/devrocks/credential-provider-oke/internal/helpers"
1214
"github.com/devrocks/credential-provider-oke/internal/provider"
15+
"github.com/gofrs/flock"
1316
)
1417

1518
func main() {
1619
var configType string
17-
flag.StringVar(&configType, "config", "", "Type the absolute path to yaml file with provider configuration. If it's ommited, program reads from environment variables (REGISTRY_TOKEN_PATH, DEFAULT_USER, REGISTRY_PROTOCOL, OCIR_AUTH_METHOD).")
20+
flag.StringVar(&configType, "config", "", "Path to config YAML or environment config")
1821
flag.Parse()
22+
23+
lockFilePath := "/var/lib/kubelet/credential-provider.lock"
24+
fileLock := flock.New(lockFilePath)
25+
26+
locked, err := fileLock.TryLock()
27+
if err != nil {
28+
os.Exit(1)
29+
}
30+
defer fileLock.Unlock()
31+
32+
if !locked {
33+
// Lock is held by another process, sleep then exit
34+
// that process will then be able to use the cache and reduce requests
35+
time.Sleep(1 * time.Second)
36+
os.Exit(0)
37+
}
38+
1939
config := helpers.ReadConfig(configType)
2040
provider.GetCredentialProviderResponse(config)
2141
}

examples/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Provider Configuration
2+
3+
## TOKEN_VALIDATION
4+
5+
To utilize token validation, set to `enabled`.
6+
7+
When the first image is pulled, the CredentialProvider will obtain a token from OCI which will be cached for subsequent image pulls up to the `cacheDuration` (normally equal to the tokens lifetime: 1hr). When the cached token expires by reaching the `cacheDuration`, the next image pull will request a new one.
8+
9+
If IAM policies are not in place at the time the token is cached then the token is essentially unauthorized for the entire duration of the `cacheDuration`. The `TOKEN_VALIDATION` configuration setting will set an initial short lived `cacheDuration` until it is determined that the cached token is authorized to pull images. Once it is determined that the token is authorized, it will set the `cacheDuration` to the tokens lifetime.
10+
11+
It is important to note that a new token is _not_ requested when the cached token expires. It is only requested when the cached token expires **and** an image pull occurs.

examples/run-locally-yaml/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ registryTokenPath: /20180419/docker/token
22
defaultUser: BEARER_TOKEN
33
registryProtocol: https
44
ocirAuthMethod: USER_PRINCIPAL
5+
tokenValidation: enabled
Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
apiVersion: kubelet.config.k8s.io/v1
22
kind: CredentialProviderConfig
33
providers:
4-
- name: credential-provider-oke
5-
apiVersion: credentialprovider.kubelet.k8s.io/v1
6-
matchImages:
7-
- "*.ocir.io"
8-
defaultCacheDuration: 55m
9-
env:
10-
- name: REGISTRY_TOKEN_PATH
11-
value: /20180419/docker/token
12-
- name: DEFAULT_USER
13-
value: BEARER_TOKEN
14-
- name: REGISTRY_PROTOCOL
15-
value: https
16-
- name: OCIR_AUTH_METHOD
17-
value: INSTANCE_PRINCIPAL
18-
4+
- name: credential-provider-oke
5+
apiVersion: credentialprovider.kubelet.k8s.io/v1
6+
matchImages:
7+
- "*.ocir.io"
8+
defaultCacheDuration: 55m
9+
env:
10+
- name: REGISTRY_TOKEN_PATH
11+
value: /20180419/docker/token
12+
- name: DEFAULT_USER
13+
value: BEARER_TOKEN
14+
- name: REGISTRY_PROTOCOL
15+
value: https
16+
- name: OCIR_AUTH_METHOD
17+
value: INSTANCE_PRINCIPAL
18+
- name: TOKEN_VALIDATION
19+
value: enabled

go.mod

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
module github.com/devrocks/credential-provider-oke
22

3-
go 1.21.7
3+
go 1.23.0
4+
5+
toolchain go1.24.4
6+
7+
require (
8+
github.com/gofrs/flock v0.12.1
9+
github.com/oracle/oci-go-sdk v24.3.0+incompatible
10+
gopkg.in/yaml.v3 v3.0.1
11+
)
412

513
require (
6-
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
7-
gopkg.in/yaml.v3 v3.0.1 // indirect
14+
github.com/kr/text v0.2.0 // indirect
15+
github.com/rogpeppe/go-internal v1.13.1 // indirect
16+
github.com/stretchr/testify v1.10.0 // indirect
17+
golang.org/x/sys v0.22.0 // indirect
818
)

go.sum

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
5+
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
6+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
7+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
8+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
9+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
110
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
211
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
12+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
13+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14+
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
15+
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
16+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
17+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
18+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
19+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
20+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
21+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
22+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
23+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
24+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
325
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
426
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/helpers/config.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,33 @@
66
package helpers
77

88
import (
9+
"bytes"
10+
"fmt"
911
"os"
1012

1113
"gopkg.in/yaml.v3"
1214
)
1315

1416
type Config struct {
17+
// ConfigMaps always use strings, even for YAML; so everything MUST be string
1518
RegistryTokenPath string `yaml:"registryTokenPath"`
1619
DefaultUser string `yaml:"defaultUser"`
1720
RegistryProtocol string `yaml:"registryProtocol"`
1821
OCIRAuthMethod string `yaml:"ocirAuthMethod"`
22+
TokenValidation string `yaml:"tokenValidation"`
23+
}
24+
25+
func (config Config) IsTokenValidationEnabled() bool {
26+
return bytes.EqualFold([]byte("enabled"), []byte(config.TokenValidation))
1927
}
2028

2129
func ReadConfig(configPath string) Config {
2230
var config Config
2331
if len(configPath) > 0 {
32+
Log("Loading config from YAML file: " + configPath)
2433
config = readConfigFromYaml(configPath)
2534
} else {
35+
Log("Loading config from environment variables.")
2636
config = readConfigFromEnv()
2737
}
2838

@@ -33,22 +43,22 @@ func ReadConfig(configPath string) Config {
3343
if config.OCIRAuthMethod == "" {
3444
config.OCIRAuthMethod = "INSTANCE_PRINCIPAL"
3545
}
46+
Log(fmt.Sprintf("Configuration Loaded: %+v", config))
3647

3748
return config
3849
}
3950

4051
func readConfigFromEnv() Config {
41-
defer Log("Configuration loaded from environment variables.")
4252
return Config{
4353
RegistryTokenPath: os.Getenv("REGISTRY_TOKEN_PATH"),
4454
DefaultUser: os.Getenv("DEFAULT_USER"),
4555
RegistryProtocol: os.Getenv("REGISTRY_PROTOCOL"),
4656
OCIRAuthMethod: os.Getenv("OCIR_AUTH_METHOD"),
57+
TokenValidation: os.Getenv("TOKEN_VALIDATION"),
4758
}
4859
}
4960

5061
func readConfigFromYaml(configPath string) Config {
51-
defer Log("Configuration loaded from YAML file: " + configPath)
5262
configYaml, err := os.ReadFile(configPath)
5363
FatalIfErrorDescription(err, "Program couldn't locate config.yaml file. Make sure to place it in the folder with the binary")
5464

internal/helpers/logger.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ func init() {
2323
}
2424

2525
func Log(message string) {
26-
Logger.Println(message)
27-
}
26+
pid := os.Getpid()
27+
Logger.Printf("[PID %d] %s", pid, message)
28+
}

internal/helpers/regex.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,25 @@
55

66
package helpers
77

8-
import "regexp"
8+
import (
9+
"fmt"
10+
"regexp"
11+
)
912

10-
func ExtractHostname(imageName string) string {
11-
regexPattern := `^(?:https?://)?(?:[^@/\n]+@)?(?:www\.)?([^:/\n]+)`
12-
regex := regexp.MustCompile(regexPattern)
13-
match := regex.FindStringSubmatch(imageName)
14-
var hostname string
15-
if len(match) > 1 {
16-
hostname = match[1]
13+
func ParseImage(image string) (hostname, repository, tag string, err error) {
14+
regexPattern := `^([a-zA-Z0-9.-]+(?::[0-9]+)?)/([^:]+)(?::(.+))?$`
15+
re := regexp.MustCompile(regexPattern)
16+
matches := re.FindStringSubmatch(image)
17+
if len(matches) == 0 {
18+
err = fmt.Errorf("invalid image format")
19+
return
1720
}
18-
return hostname
21+
22+
hostname = matches[1]
23+
repository = matches[2]
24+
tag = matches[3]
25+
if tag == "" {
26+
tag = "latest"
27+
}
28+
return
1929
}

0 commit comments

Comments
 (0)