Skip to content

Commit f2ef4e8

Browse files
authored
feat: Implement password encryption and HTTP registration for login functionality (#17)
Signed-off-by: cormick <[email protected]>
1 parent 0637c2a commit f2ef4e8

File tree

11 files changed

+175
-38
lines changed

11 files changed

+175
-38
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ Temporary Items
5656
### macOS Patch ###
5757
# iCloud generated files
5858
*.icloud
59+
60+
# output
61+
bin
62+
output

Makefile

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#/*
2+
# * Copyright 2024 The CNAI Authors
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+
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
18+
ifeq (,$(shell go env GOBIN))
19+
GOBIN=$(shell go env GOPATH)/bin
20+
else
21+
GOBIN=$(shell go env GOBIN)
22+
endif
23+
# Setting SHELL to bash allows bash commands to be executed by recipes.
24+
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
25+
SHELL = /usr/bin/env bash -o pipefail
26+
.SHELLFLAGS = -ec
27+
28+
.PHONY: all
29+
all: build
30+
31+
##@ General
32+
33+
# The help target prints out all targets with their descriptions organized
34+
# beneath their categories. The categories are represented by '##@' and the
35+
# target descriptions by '##'. The awk command is responsible for reading the
36+
# entire set of makefiles included in this invocation, looking for lines of the
37+
# file as xyz: ## something, and then pretty-format the target and help. Then,
38+
# if there's a line with ##@ something, that gets pretty-printed as a category.
39+
# More info on the usage of ANSI control characters for terminal formatting:
40+
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
41+
# More info on the awk command:
42+
# http://linuxcommand.org/lc3_adv_awk.php
43+
44+
.PHONY: help
45+
help: ## Display this help.
46+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
47+
48+
.PHONY: fmt
49+
fmt: ## Run go fmt against code.
50+
go fmt ./...
51+
52+
.PHONY: vet
53+
vet: ## Run go vet against code.
54+
go vet ./...
55+
56+
.PHONY: test
57+
test: fmt vet ## Run unit test and display the coverage.
58+
go test $$(go list ./pkg/...) -coverprofile cover.out
59+
go tool cover -func cover.out
60+
61+
GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
62+
GOLANGCI_LINT_VERSION ?= v1.54.2
63+
golangci-lint:
64+
@[ -f $(GOLANGCI_LINT) ] || { \
65+
set -e ;\
66+
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\
67+
}
68+
69+
.PHONY: lint
70+
lint: golangci-lint ## Run golangci-lint linter & yamllint
71+
$(GOLANGCI_LINT) run
72+
73+
.PHONY: lint-fix
74+
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
75+
76+
GOIMPORTS := $(shell command -v goimports 2> /dev/null)
77+
ifeq ($(GOIMPORTS),)
78+
GOIMPORTS_INSTALL = go install golang.org/x/tools/cmd/goimports@latest
79+
else
80+
GOIMPORTS_INSTALL =
81+
endif
82+
83+
$(GOIMPORTS_INSTALL):
84+
$(GOIMPORTS_INSTALL)
85+
86+
lint-fix: $(GOIMPORTS_INSTALL)
87+
$(GOIMPORTS) -l -w .
88+
$(GOLANGCI_LINT) run --fix
89+
90+
##@ Build
91+
92+
.PHONY: build
93+
build: fmt vet
94+
go build -o output/modctl main.go
95+
96+
## Location to install dependencies to
97+
LOCALBIN ?= $(shell pwd)/bin
98+
$(LOCALBIN):
99+
mkdir -p $(LOCALBIN)
100+
101+
.PHONY: gen
102+
gen: gen-mockery## Generate all we need!
103+
104+
.PHONY: gen-mockery check-mockery install-mockery
105+
gen-mockery: check-mockery ## Generate mockery code
106+
@echo "generating mockery code according to .mockery.yaml"
107+
@mockery
108+
109+
check-mockery:
110+
@which mockery > /dev/null || { echo "mockery not found. Trying to install via Homebrew..."; $(MAKE) install-mockery; }
111+
@mockery --version | grep -q "2.46.3" || { echo "mockery version is not v2.46.3. Trying to install the correct version..."; $(MAKE) install-mockery; }
112+
113+
install-mockery:
114+
@if command -v brew > /dev/null; then \
115+
echo "Installing mockery via Homebrew"; \
116+
brew install mockery; \
117+
else \
118+
echo "Error: Homebrew is not installed. Please install Homebrew first and ensure it's in your PATH."; \
119+
exit 1; \
120+
fi

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ It offers commands such as `build`, `pull`, `push`, and more, making it easy for
1111

1212
```shell
1313
$ git clone https://github.com/CloudNativeAI/modctl.git
14-
$ go build -o modctl cmd/main.go
14+
$ make
15+
$ ./output/modctl -h
1516
```
1617

1718
### Build

cmd/login.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,32 @@
1717
package cmd
1818

1919
import (
20-
"bufio"
2120
"context"
2221
"fmt"
23-
"os"
2422
"strings"
23+
"syscall"
24+
25+
"golang.org/x/crypto/ssh/terminal"
2526

2627
"github.com/CloudNativeAI/modctl/pkg/backend"
2728
"github.com/CloudNativeAI/modctl/pkg/config"
28-
2929
"github.com/spf13/cobra"
3030
"github.com/spf13/viper"
3131
)
3232

3333
var loginConfig = config.NewLogin()
3434

35-
type loginOptions struct {
36-
username string
37-
password string
38-
passwordStdin bool
39-
}
40-
4135
// loginCmd represents the modctl command for login.
4236
var loginCmd = &cobra.Command{
43-
Use: "login [flags] <registry>",
44-
Short: "A command line tool for modctl login",
37+
Use: "login [flags] <registry>",
38+
Short: "A command line tool for modctl login",
39+
Example: `
40+
# login to docker hub:
41+
modctl login -u foo registry-1.docker.io
42+
43+
# login to insecure register:
44+
modctl login -u foo --insecure registry-insecure.io
45+
`,
4546
Args: cobra.ExactArgs(1),
4647
DisableAutoGenTag: true,
4748
SilenceUsage: true,
@@ -51,7 +52,7 @@ var loginCmd = &cobra.Command{
5152
return err
5253
}
5354

54-
return runLogin(context.Background(), args[0])
55+
return runLogin(cmd.Context(), args[0])
5556
},
5657
}
5758

@@ -60,7 +61,8 @@ func init() {
6061
flags := loginCmd.Flags()
6162
flags.StringVarP(&loginConfig.Username, "username", "u", "", "Username for login")
6263
flags.StringVarP(&loginConfig.Password, "password", "p", "", "Password for login")
63-
flags.BoolVar(&loginConfig.PasswordStdin, "password-stdin", false, "Take the password from stdin")
64+
flags.BoolVar(&loginConfig.PasswordStdin, "password-stdin", true, "Take the password from stdin by default")
65+
flags.BoolVar(&loginConfig.Insecure, "insecure", false, "Allow insecure connections to registry")
6466

6567
if err := viper.BindPFlags(flags); err != nil {
6668
panic(fmt.Errorf("bind cache login flags to viper: %w", err))
@@ -75,18 +77,18 @@ func runLogin(ctx context.Context, registry string) error {
7577
}
7678

7779
// read password from stdin if password-stdin is set
78-
if loginConfig.PasswordStdin {
80+
if loginConfig.PasswordStdin && loginConfig.Password == "" {
7981
fmt.Print("Enter password: ")
80-
reader := bufio.NewReader(os.Stdin)
81-
password, err := reader.ReadString('\n')
82+
password, err := terminal.ReadPassword(syscall.Stdin)
8283
if err != nil {
8384
return err
8485
}
8586

86-
loginConfig.Password = strings.TrimSpace(password)
87+
loginConfig.Password = strings.TrimSpace(string(password))
8788
}
8889

89-
if err := b.Login(ctx, registry, loginConfig.Username, loginConfig.Password); err != nil {
90+
fmt.Println("\nLogging In...")
91+
if err := b.Login(ctx, registry, loginConfig.Username, loginConfig.Password, loginConfig.Insecure); err != nil {
9092
return err
9193
}
9294

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/spf13/viper v1.19.0
1414
github.com/stretchr/testify v1.9.0
1515
github.com/vbauerster/mpb/v8 v8.8.3
16+
golang.org/x/crypto v0.28.0
1617
oras.land/oras-go/v2 v2.5.0
1718
zotregistry.dev/zot v1.4.3
1819
)
@@ -114,6 +115,7 @@ require (
114115
golang.org/x/net v0.29.0 // indirect
115116
golang.org/x/sync v0.8.0 // indirect
116117
golang.org/x/sys v0.26.0 // indirect
118+
golang.org/x/term v0.25.0 // indirect
117119
golang.org/x/text v0.19.0 // indirect
118120
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
119121
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect

pkg/backend/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
// Backend is the interface to represent the backend.
2626
type Backend interface {
2727
// Login logs into a registry.
28-
Login(ctx context.Context, registry, username, password string) error
28+
Login(ctx context.Context, registry, username, password string, insecure bool) error
2929

3030
// Logout logs out from a registry.
3131
Logout(ctx context.Context, registry string) error

pkg/backend/login.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@ package backend
1818

1919
import (
2020
"context"
21-
2221
"oras.land/oras-go/v2/registry/remote"
2322
"oras.land/oras-go/v2/registry/remote/auth"
2423
"oras.land/oras-go/v2/registry/remote/credentials"
2524
)
2625

2726
// Login logs into a registry.
28-
func (b *backend) Login(ctx context.Context, registry, username, password string) error {
27+
func (b *backend) Login(ctx context.Context, registry, username, password string, insecure bool) error {
2928
// read credentials from docker store.
3029
store, err := credentials.NewStoreFromDocker(credentials.StoreOptions{AllowPlaintextPut: true})
3130
if err != nil {
@@ -36,6 +35,7 @@ func (b *backend) Login(ctx context.Context, registry, username, password string
3635
if err != nil {
3736
return err
3837
}
38+
reg.PlainHTTP = insecure
3939

4040
cred := auth.Credential{
4141
Username: username,

pkg/config/login.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ type Login struct {
2222
Username string
2323
Password string
2424
PasswordStdin bool
25+
Insecure bool
2526
}
2627

2728
func NewLogin() *Login {
2829
return &Login{
2930
Username: "",
3031
Password: "",
31-
PasswordStdin: false,
32+
PasswordStdin: true,
33+
Insecure: false,
3234
}
3335
}
3436

@@ -37,7 +39,7 @@ func (l *Login) Validate() error {
3739
return fmt.Errorf("missing username")
3840
}
3941

40-
if len(l.Password) == 0 {
42+
if len(l.Password) == 0 && !l.PasswordStdin {
4143
return fmt.Errorf("missing password")
4244
}
4345

pkg/config/login_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ func TestNewLogin(t *testing.T) {
2828
if login.Password != "" {
2929
t.Errorf("expected empty password, got %s", login.Password)
3030
}
31-
if login.PasswordStdin != false {
32-
t.Errorf("expected PasswordStdin to be false, got %v", login.PasswordStdin)
31+
if login.PasswordStdin != true {
32+
t.Errorf("expected PasswordStdin to be true, got %v", login.PasswordStdin)
3333
}
3434
}
3535

pkg/modelfile/modelfile_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package modelfile
1919
import (
2020
"errors"
2121
"os"
22+
"sort"
2223
"testing"
2324

2425
"github.com/stretchr/testify/assert"
@@ -199,8 +200,12 @@ name bar
199200

200201
assert.NoError(err)
201202
assert.NotNil(mf)
202-
assert.Equal(tc.configs, mf.GetConfigs())
203-
assert.Equal(tc.models, mf.GetModels())
203+
configs := mf.GetConfigs()
204+
models := mf.GetModels()
205+
sort.Strings(configs)
206+
sort.Strings(models)
207+
assert.Equal(tc.configs, configs)
208+
assert.Equal(tc.models, models)
204209
assert.Equal(tc.name, mf.GetName())
205210
assert.Equal(tc.arch, mf.GetArch())
206211
assert.Equal(tc.family, mf.GetFamily())

0 commit comments

Comments
 (0)