Skip to content

Commit 7f3d0a8

Browse files
committed
feat: Implement password encryption and HTTP registration for login functionality
Signed-off-by: cormick <[email protected]>
1 parent 0637c2a commit 7f3d0a8

File tree

12 files changed

+161
-26
lines changed

12 files changed

+161
-26
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 cmd/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+
110+
check-mockery:
111+
@which mockery > /dev/null || { echo "mockery not found. Trying to install via Homebrew..."; 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: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,21 @@
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{
4337
Use: "login [flags] <registry>",
@@ -51,7 +45,7 @@ var loginCmd = &cobra.Command{
5145
return err
5246
}
5347

54-
return runLogin(context.Background(), args[0])
48+
return runLogin(cmd.Context(), args[0])
5549
},
5650
}
5751

@@ -60,7 +54,7 @@ func init() {
6054
flags := loginCmd.Flags()
6155
flags.StringVarP(&loginConfig.Username, "username", "u", "", "Username for login")
6256
flags.StringVarP(&loginConfig.Password, "password", "p", "", "Password for login")
63-
flags.BoolVar(&loginConfig.PasswordStdin, "password-stdin", false, "Take the password from stdin")
57+
flags.BoolVar(&loginConfig.PasswordStdin, "password-stdin", true, "Take the password from stdin by default")
6458

6559
if err := viper.BindPFlags(flags); err != nil {
6660
panic(fmt.Errorf("bind cache login flags to viper: %w", err))
@@ -75,17 +69,17 @@ func runLogin(ctx context.Context, registry string) error {
7569
}
7670

7771
// read password from stdin if password-stdin is set
78-
if loginConfig.PasswordStdin {
72+
if loginConfig.PasswordStdin && loginConfig.Password == "" {
7973
fmt.Print("Enter password: ")
80-
reader := bufio.NewReader(os.Stdin)
81-
password, err := reader.ReadString('\n')
74+
password, err := terminal.ReadPassword(syscall.Stdin)
8275
if err != nil {
8376
return err
8477
}
8578

86-
loginConfig.Password = strings.TrimSpace(password)
79+
loginConfig.Password = strings.TrimSpace(string(password))
8780
}
8881

82+
fmt.Println("\nLogging In...")
8983
if err := b.Login(ctx, registry, loginConfig.Username, loginConfig.Password); err != nil {
9084
return err
9185
}

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/login.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package backend
1818

1919
import (
2020
"context"
21+
"net/url"
2122

2223
"oras.land/oras-go/v2/registry/remote"
2324
"oras.land/oras-go/v2/registry/remote/auth"
@@ -26,17 +27,25 @@ import (
2627

2728
// Login logs into a registry.
2829
func (b *backend) Login(ctx context.Context, registry, username, password string) error {
30+
registryUrl, err := url.ParseRequestURI(registry)
31+
if err != nil {
32+
return err
33+
}
2934
// read credentials from docker store.
3035
store, err := credentials.NewStoreFromDocker(credentials.StoreOptions{AllowPlaintextPut: true})
3136
if err != nil {
3237
return err
3338
}
3439

35-
reg, err := remote.NewRegistry(registry)
40+
reg, err := remote.NewRegistry(registryUrl.Host)
3641
if err != nil {
3742
return err
3843
}
3944

45+
if registryUrl.Scheme == "http" {
46+
reg.PlainHTTP = true
47+
}
48+
4049
cred := auth.Credential{
4150
Username: username,
4251
Password: password,

pkg/config/login.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func NewLogin() *Login {
2828
return &Login{
2929
Username: "",
3030
Password: "",
31-
PasswordStdin: false,
31+
PasswordStdin: true,
3232
}
3333
}
3434

@@ -37,7 +37,7 @@ func (l *Login) Validate() error {
3737
return fmt.Errorf("missing username")
3838
}
3939

40-
if len(l.Password) == 0 {
40+
if len(l.Password) == 0 && !l.PasswordStdin {
4141
return fmt.Errorf("missing password")
4242
}
4343

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())

test/mocks/backend/backend.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)