Skip to content

Commit 1118e3f

Browse files
authored
OCM-4965: Secure store tweaks (#905)
1 parent 3925489 commit 1118e3f

File tree

3 files changed

+149
-37
lines changed

3 files changed

+149
-37
lines changed

authentication/securestore/main.go

+77-34
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,32 @@ import (
55
"compress/gzip"
66
"fmt"
77
"io"
8+
"strings"
89

910
"github.com/99designs/keyring"
1011
)
1112

1213
const (
13-
SecureStoreConfigKey = "securestore" // OCM_CONFIG key to enable secure OS store
1414
KindInternetPassword = "Internet password" // MacOS Keychain item kind
1515
ItemKey = "RedHatSSO"
1616
CollectionName = "login" // Common OS default collection name
1717
MaxWindowsByteSize = 2500 // Windows Credential Manager has a 2500 byte limit
1818
)
1919

2020
var (
21-
ErrNoBackendsAvailable = fmt.Errorf("no backends available, expected one of %v", allowedBackends)
22-
// The order of the backends is important. The first backend in the list is the first one
23-
// that will attempt to be used.
24-
allowedBackends = []keyring.BackendType{
25-
keyring.WinCredBackend,
26-
keyring.KeychainBackend,
27-
keyring.SecretServiceBackend,
28-
keyring.PassBackend,
21+
ErrKeyringUnavailable = fmt.Errorf("keyring is valid but is not available on the current OS")
22+
ErrKeyringInvalid = fmt.Errorf("keyring is invalid, expected one of: [%v]", strings.Join(AllowedBackends, ", "))
23+
AllowedBackends = []string{
24+
string(keyring.WinCredBackend),
25+
string(keyring.KeychainBackend),
26+
string(keyring.SecretServiceBackend),
27+
string(keyring.PassBackend),
2928
}
3029
)
3130

32-
func getKeyringConfig() keyring.Config {
31+
func getKeyringConfig(backend string) keyring.Config {
3332
return keyring.Config{
34-
AllowedBackends: allowedBackends,
33+
AllowedBackends: []keyring.BackendType{keyring.BackendType(backend)},
3534
// Generic
3635
ServiceName: ItemKey,
3736
// MacOS
@@ -46,35 +45,51 @@ func getKeyringConfig() keyring.Config {
4645
}
4746
}
4847

49-
// AvailableBackends provides a slice of all available backend keys on the current OS.
48+
// IsBackendAvailable provides validation that the desired backend is available on the current OS.
5049
//
51-
// Note: CGO_ENABLED=1 is required for OSX Keychain and darwin builds
50+
// Note: CGO_ENABLED=1 is required for darwin builds (enables OSX Keychain)
51+
func IsBackendAvailable(backend string) (isAvailable bool) {
52+
if backend == "" {
53+
return false
54+
}
55+
56+
for _, avail := range AvailableBackends() {
57+
if avail == backend {
58+
isAvailable = true
59+
break
60+
}
61+
}
62+
63+
return isAvailable
64+
}
65+
66+
// AvailableBackends provides a slice of all available backend keys on the current OS.
5267
//
53-
// The first backend in the slice is the first one that will be used.
68+
// Note: CGO_ENABLED=1 is required for darwin builds (enables OSX Keychain)
5469
func AvailableBackends() []string {
5570
b := []string{}
5671

5772
// Intersection between available backends from OS and allowed backends
5873
for _, avail := range keyring.AvailableBackends() {
59-
for _, allowed := range allowedBackends {
60-
if avail == allowed {
61-
b = append(b, string(allowed))
74+
for _, allowed := range AllowedBackends {
75+
if string(avail) == allowed {
76+
b = append(b, allowed)
6277
}
6378
}
6479
}
6580

6681
return b
6782
}
6883

69-
// UpsertConfigToKeyring will upsert the provided credentials to first priority OS secure store.
84+
// UpsertConfigToKeyring will upsert the provided credentials to the desired OS secure store.
7085
//
71-
// Note: CGO_ENABLED=1 is required for OSX Keychain and darwin builds
72-
func UpsertConfigToKeyring(creds []byte) error {
73-
if err := validateBackends(); err != nil {
86+
// Note: CGO_ENABLED=1 is required for darwin builds (enables OSX Keychain)
87+
func UpsertConfigToKeyring(backend string, creds []byte) error {
88+
if err := ValidateBackend(backend); err != nil {
7489
return err
7590
}
7691

77-
ring, err := keyring.Open(getKeyringConfig())
92+
ring, err := keyring.Open(getKeyringConfig(backend))
7893
if err != nil {
7994
return err
8095
}
@@ -86,7 +101,7 @@ func UpsertConfigToKeyring(creds []byte) error {
86101

87102
// check if available backend contains windows credential manager and exceeds the byte limit
88103
if len(compressed) > MaxWindowsByteSize &&
89-
keyring.AvailableBackends()[0] == keyring.WinCredBackend {
104+
backend == string(keyring.WinCredBackend) {
90105
return fmt.Errorf("credentials are too large for Windows Credential Manager: %d bytes (max %d)", len(compressed), MaxWindowsByteSize)
91106
}
92107

@@ -103,32 +118,42 @@ func UpsertConfigToKeyring(creds []byte) error {
103118
// RemoveConfigFromKeyring will remove the credentials from the first priority OS secure store.
104119
//
105120
// Note: CGO_ENABLED=1 is required for OSX Keychain and darwin builds
106-
func RemoveConfigFromKeyring() error {
107-
if err := validateBackends(); err != nil {
121+
func RemoveConfigFromKeyring(backend string) error {
122+
if err := ValidateBackend(backend); err != nil {
108123
return err
109124
}
110125

111-
ring, err := keyring.Open(getKeyringConfig())
126+
ring, err := keyring.Open(getKeyringConfig(backend))
112127
if err != nil {
113128
return err
114129
}
115130

116131
err = ring.Remove(ItemKey)
132+
if err != nil {
133+
if err == keyring.ErrKeyNotFound {
134+
// Ignore not found errors, key is already removed
135+
return nil
136+
}
137+
138+
if strings.Contains(err.Error(), "Keychain Error. (-25244)") {
139+
return fmt.Errorf("%s\nThis application may not have permission to delete from the Keychain. Please check the permissions in the Keychain and try again", err.Error())
140+
}
141+
}
117142

118143
return err
119144
}
120145

121146
// GetConfigFromKeyring will retrieve the credentials from the first priority OS secure store.
122147
//
123-
// Note: CGO_ENABLED=1 is required for OSX Keychain and darwin builds
124-
func GetConfigFromKeyring() ([]byte, error) {
125-
if err := validateBackends(); err != nil {
148+
// Note: CGO_ENABLED=1 is required for darwin builds (enables OSX Keychain)
149+
func GetConfigFromKeyring(backend string) ([]byte, error) {
150+
if err := ValidateBackend(backend); err != nil {
126151
return nil, err
127152
}
128153

129154
credentials := []byte("")
130155

131-
ring, err := keyring.Open(getKeyringConfig())
156+
ring, err := keyring.Open(getKeyringConfig(backend))
132157
if err != nil {
133158
return nil, err
134159
}
@@ -156,11 +181,29 @@ func GetConfigFromKeyring() ([]byte, error) {
156181

157182
}
158183

159-
// Validates that at least one backend is available
160-
func validateBackends() error {
161-
if len(AvailableBackends()) == 0 {
162-
return ErrNoBackendsAvailable
184+
// Validates that the requested backend is valid and available, returns an error if not.
185+
//
186+
// Note: CGO_ENABLED=1 is required for darwin builds (enables OSX Keychain)
187+
func ValidateBackend(backend string) error {
188+
if backend == "" {
189+
return ErrKeyringInvalid
190+
} else {
191+
isAllowedBackend := false
192+
for _, allowed := range AllowedBackends {
193+
if allowed == backend {
194+
isAllowedBackend = true
195+
break
196+
}
197+
}
198+
if !isAllowedBackend {
199+
return ErrKeyringInvalid
200+
}
201+
}
202+
203+
if !IsBackendAvailable(backend) {
204+
return ErrKeyringUnavailable
163205
}
206+
164207
return nil
165208
}
166209

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright (c) 2024 Red Hat, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// This file contains tests for the functions that extract authentication and authorization
18+
// information from contexts.
19+
20+
package securestore
21+
22+
import (
23+
"testing"
24+
25+
. "github.com/onsi/ginkgo/v2/dsl/core" // nolint
26+
. "github.com/onsi/gomega" // nolint
27+
)
28+
29+
func TestSecurestore(t *testing.T) {
30+
RegisterFailHandler(Fail)
31+
RunSpecs(t, "Authentication.Securestore")
32+
}
33+
34+
var _ = Describe("Validation", func() {
35+
It("Validates an invalid backend", func() {
36+
err := ValidateBackend("invalid")
37+
Expect(err).To(Equal(ErrKeyringInvalid))
38+
39+
err = UpsertConfigToKeyring("invalid", []byte("test"))
40+
Expect(err).To(Equal(ErrKeyringInvalid))
41+
42+
bytes, err := GetConfigFromKeyring("invalid")
43+
Expect(err).To(Equal(ErrKeyringInvalid))
44+
Expect(bytes).To(BeNil())
45+
46+
err = RemoveConfigFromKeyring("invalid")
47+
Expect(err).To(Equal(ErrKeyringInvalid))
48+
})
49+
50+
It("Validates an empty backend", func() {
51+
err := ValidateBackend("")
52+
Expect(err).To(Equal(ErrKeyringInvalid))
53+
54+
err = UpsertConfigToKeyring("", []byte("test"))
55+
Expect(err).To(Equal(ErrKeyringInvalid))
56+
57+
bytes, err := GetConfigFromKeyring("")
58+
Expect(err).To(Equal(ErrKeyringInvalid))
59+
Expect(bytes).To(BeNil())
60+
61+
err = RemoveConfigFromKeyring("")
62+
Expect(err).To(Equal(ErrKeyringInvalid))
63+
})
64+
65+
})

examples/secure_store.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import (
99
)
1010

1111
func main() {
12+
13+
// Modify the following variable to match your OS:
14+
keyring := "keychain"
15+
1216
// Create a context:
1317
clientId := "ocm-cli"
1418

@@ -27,22 +31,22 @@ func main() {
2731
config := []byte("mybytestringagain")
2832

2933
// Upsert to keyring
30-
err = securestore.UpsertConfigToKeyring(config)
34+
err = securestore.UpsertConfigToKeyring(keyring, config)
3135
if err != nil {
3236
fmt.Fprintf(os.Stderr, "Error upserting to keyring: %v", err)
3337
os.Exit(1)
3438
}
3539

3640
// Upsert again to keyring
3741
config = []byte(token)
38-
err = securestore.UpsertConfigToKeyring(config)
42+
err = securestore.UpsertConfigToKeyring(keyring, config)
3943
if err != nil {
4044
fmt.Fprintf(os.Stderr, "Error upserting to keyring: %v", err)
4145
os.Exit(1)
4246
}
4347

4448
// Read bytes back from Keyring
45-
readVal, err := securestore.GetConfigFromKeyring()
49+
readVal, err := securestore.GetConfigFromKeyring(keyring)
4650
if err != nil {
4751
fmt.Fprintf(os.Stderr, "Error reading from keyring: %v", err)
4852
os.Exit(1)

0 commit comments

Comments
 (0)