Skip to content

Commit

Permalink
OCM-4965: Keyring configuration storage
Browse files Browse the repository at this point in the history
  • Loading branch information
tylercreller committed Feb 19, 2024
1 parent fab7ccf commit 3689439
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 40 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/check-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ jobs:
run: go mod download
- name: Setup Ginkgo
run: go install github.com/onsi/ginkgo/v2/ginkgo@v2.9
- name: Run the tests
- name: Run the tests (linux, windows)
if: ${{ contains(fromJSON('["ubuntu-latest", "windows-latest"]'), matrix.platform) }}
run: make tests
- name: Run the tests (macOS-only)
if: ${{ contains(fromJSON('["macos-latest"]'), matrix.platform) }}
run: make tests-cgo

golangci:
name: Lint
Expand Down
114 changes: 109 additions & 5 deletions .github/workflows/publish-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ on:
- '*'

jobs:

release:
name: Publish release
release-linux-windows:
name: Publish release (Linux and Windows)
runs-on: ubuntu-latest
steps:
- name: Checkout the source
Expand Down Expand Up @@ -85,8 +84,6 @@ jobs:
os.rename(binary, asset)
# Build for the supported operating systems and architectures:
build("darwin", "amd64")
build("darwin", "arm64")
build("linux", "amd64")
build("linux", "arm64")
build("linux", "ppc64le")
Expand Down Expand Up @@ -152,3 +149,110 @@ jobs:
),
)
response.raise_for_status()
release-macos:
needs: release-linux-windows
name: Publish release (macOS)
runs-on: macos-latest
steps:
- name: Checkout the source
uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
cache: 'pip'

- name: Install Python modules
run: pip install -r .github/workflows/requirements.txt

- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18

- name: Create release
shell: python
run: |
import os
import re
import requests
import shutil
import subprocess
# Get the context and secret data that we will need:
repository = "${{ github.repository }}"
reference = "${{ github.ref }}"
token = "${{ secrets.GITHUB_TOKEN }}"
# Calculate the version number:
version = re.sub(r"^refs/tags/v(.*)$", r"\1", reference)
# Make sure that the assets directory exists and is empty:
assets = "assets"
shutil.rmtree(assets, ignore_errors=True)
os.mkdir(assets)
def build(goos: str, goarch: str):
# Set the environment variables that tell the Go compiler which
# operating system and architecture to build for:
env = dict(os.environ)
env["GOOS"] = goos
env["GOARCH"] = goarch
# Build the binary:
args = ["make", "cmds-cgo"]
subprocess.run(check=True, env=env, args=args)
# Copy the generated binary to the assets directory:
binary = "ocm"
asset = os.path.join(assets, f"ocm-{goos}-{goarch}")
os.rename(binary, asset)
# Build for the supported operating systems and architectures:
build("darwin", "amd64")
build("darwin", "arm64")
# Calculate the SHA256 digests:
for asset in os.listdir(assets):
digest = os.path.join(assets, f"{asset}.sha256")
with open(digest, "wb") as stream:
args = ["shasum", "-a", "256"]
subprocess.run(check=True, cwd=assets, stdout=stream, args=args)
# Get the release:
response = requests.get(
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
},
url=(
"https://api.github.com"
f"/repos/{repository}/releases/tags/v{version}"
),
)
response.raise_for_status()
# Get the release identifier:
release = response.json()["id"]
# Upload the assets:
for asset in os.listdir(assets):
file = os.path.join(assets, asset)
with open(file, "rb") as stream:
response = requests.post(
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/octet-stream",
"Accept": "application/json",
},
data=stream,
url=(
"https://uploads.github.com"
f"/repos/{repository}/releases/{release}/assets?name={asset}"
),
)
response.raise_for_status()
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,31 @@ cmds:
go build "./cmd/$${cmd}" || exit 1; \
done

# Used for compiling with CGO_ENABLED=1 (macOS keychain support)
.PHONY: cmds-cgo
cmds-cgo:
for cmd in $$(ls cmd); do \
CGO_ENABLED=1 \
go build "./cmd/$${cmd}" || exit 1; \
done

.PHONY: install
install:
go install ./cmd/ocm

# CGO_ENABLED=1 is required for keychain support on macOS
.PHONY: install-cgo
install-cgo:
CGO_ENABLED=1 go install ./cmd/ocm

.PHONY: test tests
test tests: cmds
ginkgo run -r

.PHONY: test-cgo tests-cgo
test-cgo tests-cgo: cmds-cgo
ginkgo run -r

.PHONY: fmt
fmt:
gofmt -s -l -w cmd pkg tests
Expand Down
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ An `~/config/ocm/ocm.json` file stores login credentials for a single API
server. Using multiple servers therefore requires having to log in and out a lot
or the ability to utilize multiple config files. The latter functionality is
provided with the `OCM_CONFIG` environment variable. If running `ocm login` was
successfull in both cases, the `ocm whoami` commands will return different
successful in both cases, the `ocm whoami` commands will return different
results:

```
Expand All @@ -129,6 +129,38 @@ $ OCM_CONFIG=$HOME/ocm.json.stg ocm whoami

NOTE: Tokens for production and staging will differ.

## Storing Configuration & Tokens in OS Keyring
The `RH_KEYRING` environment variable provides the ability to store the OCM
configuration containing your tokens in your OS keyring. This is provided
as an alternative to storing the configuration in plain-text on your system.

`RH_KEYRING` supports the following keyrings:

* [Windows Credential Manager](https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0) - `wincred`
* [macOS Keychain](https://support.apple.com/en-us/guide/keychain-access/welcome/mac) - `keychain`
* Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://apps.kde.org/kwalletmanager5/), etc.) - `secret-service`
* [Pass](https://www.passwordstore.org/) - `pass`

| | wincred | keychain | secret-service | pass |
| ------------- | ------------- | ------------- | ------------- | ------------- |
| Windows | :heavy_check_mark: | :x: | :x: | :x: |
| macOS | :x: | :heavy_check_mark:* | :x: | :heavy_check_mark: |
| Linux | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |

<sub>* if building from source CGO_ENABLED=1 is required for macOS keychain support.</sub>

#### See Available Keyrings
The following will list available keyrings in your current context
```
$ ocm config get keyrings
```

#### Remove Keyring Configuration
The following will remove the OCM configuration from your `RH_KEYRING`
```
$ ocm config reset keyring
```

## Obtaining Tokens

If you need the _OpenID_ access token to use it with some other tool, you can
Expand Down
2 changes: 2 additions & 0 deletions cmd/ocm/config/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"

"github.com/openshift-online/ocm-cli/cmd/ocm/config/get"
"github.com/openshift-online/ocm-cli/cmd/ocm/config/reset"
"github.com/openshift-online/ocm-cli/cmd/ocm/config/set"
"github.com/openshift-online/ocm-cli/pkg/config"
)
Expand Down Expand Up @@ -73,4 +74,5 @@ var Cmd = &cobra.Command{
func init() {
Cmd.AddCommand(get.Cmd)
Cmd.AddCommand(set.Cmd)
Cmd.AddCommand(reset.Cmd)
}
40 changes: 31 additions & 9 deletions cmd/ocm/config/get/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"

"github.com/openshift-online/ocm-cli/pkg/config"
"github.com/openshift-online/ocm-sdk-go/authentication/securestore"
)

var args struct {
Expand All @@ -48,17 +49,25 @@ func init() {
}

func run(cmd *cobra.Command, argv []string) error {
// Load the configuration file:
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("Can't load config file: %v", err)
// The following variables are not stored in the configuration file
// and can skip loading configuration:
skipConfigLoadMap := map[string]bool{
"keyrings": true,
}

// If the configuration file doesn't exist yet assume that all the configuration settings
// are empty:
if cfg == nil {
fmt.Printf("\n")
return nil
cfg := config.Config{}
if !skipConfigLoadMap[argv[0]] {
// Load the configuration file:
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("Can't load config file: %v", err)
}
// If the configuration file doesn't exist yet assume that all the configuration settings
// are empty:
if cfg == nil {
fmt.Printf("\n")
return nil
}
}

// Print the value of the requested configuration setting:
Expand All @@ -83,9 +92,22 @@ func run(cmd *cobra.Command, argv []string) error {
fmt.Fprintf(os.Stdout, "%s\n", cfg.URL)
case "pager":
fmt.Fprintf(os.Stdout, "%s\n", cfg.Pager)
case "keyrings":
keyrings := getKeyrings()
for _, keyring := range keyrings {
fmt.Println(keyring)
}
default:
return fmt.Errorf("Unknown setting")
}

return nil
}

func getKeyrings() []string {
backends := securestore.AvailableBackends()
if len(backends) == 0 {
fmt.Printf("Error: No keyrings available\n")
}
return backends
}
66 changes: 66 additions & 0 deletions cmd/ocm/config/reset/reset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright (c) 2024 Red Hat, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package reset

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/openshift-online/ocm-sdk-go/authentication/securestore"
)

var args struct {
debug bool
}

var Cmd = &cobra.Command{
Use: "reset [flags] VARIABLE",
Short: "Resets/removes the requested option from configuration",
Long: "Resets/removes requested option from configuration",
Args: cobra.ExactArgs(1),
RunE: run,
}

func init() {
flags := Cmd.Flags()
flags.BoolVar(
&args.debug,
"debug",
false,
"Enable debug mode.",
)
}

func run(cmd *cobra.Command, argv []string) error {
switch argv[0] {
case "keyring":
keyring := os.Getenv("RH_KEYRING")
if keyring == "" {
return fmt.Errorf("RH_KEYRING is required to reset config")
}
err := securestore.RemoveConfigFromKeyring(keyring)
if err != nil {
return fmt.Errorf("can't reset keyring: %v", err)
}
default:
return fmt.Errorf("unknown setting")
}

return nil
}

0 comments on commit 3689439

Please sign in to comment.