first commit
This commit is contained in:
commit
4d56d8ea21
134
DESIGN.md
Normal file
134
DESIGN.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
Network policy and (later) linkerd policy generator.
|
||||||
|
|
||||||
|
Basic idea:
|
||||||
|
1. The kubernetes clusters hosts applications
|
||||||
|
2. Applications communicate to other applications
|
||||||
|
|
||||||
|
Allowed communication betwen applications is configured as follows:
|
||||||
|
|
||||||
|
communication:
|
||||||
|
- from: app1
|
||||||
|
to: app2
|
||||||
|
ports:
|
||||||
|
- 80
|
||||||
|
- linkerd-admin
|
||||||
|
|
||||||
|
Ports are optional. When omitted all ports are intended
|
||||||
|
|
||||||
|
There are pre-defined applications such as api-server.
|
||||||
|
Beyond that thera are two types of applications:
|
||||||
|
- cidr: a cidr with possible except clauses following netpol syntax
|
||||||
|
- app: regular app based on matchLabels, together with namespace name.
|
||||||
|
|
||||||
|
|
||||||
|
Application names must be unique.
|
||||||
|
|
||||||
|
There are also standard capablities for an application such as:
|
||||||
|
* linkerd: addes egress to linkerd-jaeger, egress to linkerd, ingress from
|
||||||
|
linkerd-viz
|
||||||
|
|
||||||
|
capablities can also be defined at the namespace level, which means they
|
||||||
|
apply to each pod in the namespace.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- name: internet
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
except:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
- 172.16.0.0/12
|
||||||
|
- 192.168.0.0/16
|
||||||
|
|
||||||
|
|
||||||
|
namespaces:
|
||||||
|
- namespace: wamblee-org
|
||||||
|
capabilities:
|
||||||
|
- linkerd
|
||||||
|
applications:
|
||||||
|
- name: nexus-server
|
||||||
|
# ports when specified at the application level are used when
|
||||||
|
# not explicitly mentioned when a link is made
|
||||||
|
ports:
|
||||||
|
- 8081
|
||||||
|
- 8082
|
||||||
|
matchLabels:
|
||||||
|
app: nexus-server
|
||||||
|
|
||||||
|
- namespace: exposure
|
||||||
|
applications:
|
||||||
|
- name: httpd-wamblee-org
|
||||||
|
matchLabels:
|
||||||
|
app: wamblee-org
|
||||||
|
|
||||||
|
communications:
|
||||||
|
- from: # can we support both string and list of strings?
|
||||||
|
- httpd-wamblee-org
|
||||||
|
to:
|
||||||
|
- nexus-server
|
||||||
|
- wamblee-static
|
||||||
|
- wamblee-safe
|
||||||
|
|
||||||
|
# or limiting ports further
|
||||||
|
- from:
|
||||||
|
- httpd-wamblee-org
|
||||||
|
to:
|
||||||
|
- nexus-server:8081,8082
|
||||||
|
|
||||||
|
|
||||||
|
Handling of capabilities:
|
||||||
|
1. capabilities at namespace level apply to each individual pod in the
|
||||||
|
namespace
|
||||||
|
2. a capability is a list of templates.
|
||||||
|
|
||||||
|
Ingress template
|
||||||
|
|
||||||
|
from:
|
||||||
|
- linkerd-viz
|
||||||
|
to:
|
||||||
|
- {{ application }}
|
||||||
|
|
||||||
|
egress template
|
||||||
|
|
||||||
|
from:
|
||||||
|
- {{ application }}
|
||||||
|
to:
|
||||||
|
- linkerd-jaeger
|
||||||
|
- linkerd
|
||||||
|
|
||||||
|
The templates are evaluated for an application and then parsed, and added
|
||||||
|
to the allowed communications.
|
||||||
|
|
||||||
|
|
||||||
|
Linkerd extension:
|
||||||
|
* for each application an optional service account is defined, when not
|
||||||
|
defined, 'default' is assumed.
|
||||||
|
* together with the communication links this determines the authorization
|
||||||
|
policies.
|
||||||
|
|
||||||
|
Technical realization could be in the form of go templates where the input
|
||||||
|
the template is:
|
||||||
|
1. map of app name to definition with for each app also a list of
|
||||||
|
capabilities.
|
||||||
|
2. a single app config with for each app
|
||||||
|
* all ingresses
|
||||||
|
* all egresses
|
||||||
|
|
||||||
|
Similar and easier technical solution via interface with a network policy
|
||||||
|
implementation and a linkerd implementation.
|
||||||
|
|
||||||
|
Then we can have:
|
||||||
|
1. networkpolicy templates
|
||||||
|
2. server, meshtls, networkauthentication, authorization policy
|
||||||
|
* when linkerd capability for both pods, the tool can generated a
|
||||||
|
meshtlsauthentication, when the target pods does not have linkerd
|
||||||
|
capability, it can be ignored.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
29
Makefile
Normal file
29
Makefile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.DEFAULT_GOAL := all
|
||||||
|
|
||||||
|
# seems superfluous
|
||||||
|
#.PHONY: fmt vet build clean
|
||||||
|
|
||||||
|
tools:
|
||||||
|
GOPROXY=direct go install git.wamblee.org/public/gotools/cmd/go2junit@main
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
vet: fmt
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
|
||||||
|
build: vet
|
||||||
|
mkdir -p bin
|
||||||
|
go build -o bin ./cmd/...
|
||||||
|
|
||||||
|
install:
|
||||||
|
go install ./...
|
||||||
|
|
||||||
|
test: build
|
||||||
|
go test -count=1 -coverprofile=testout/coverage.out -json ${TESTFLAGS} ./... | go2junit testout
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf bin
|
||||||
|
|
||||||
|
all: build
|
45
cmd/policygen/config.go
Normal file
45
cmd/policygen/config.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateCIDR(cidr string) error {
|
||||||
|
_, _, err := net.ParseCIDR(cidr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type CIDR string
|
||||||
|
|
||||||
|
// UnmarshalYAML implements the yaml.Unmarshaler interface
|
||||||
|
func (c *CIDR) UnmarshalYAML(value []byte) error {
|
||||||
|
// Get the string value from the node
|
||||||
|
var s string
|
||||||
|
if err := yaml.Unmarshal(value, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateCIDR(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = CIDR(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML implements the yaml.Marshaler interface
|
||||||
|
func (c CIDR) MarshalYAML() ([]byte, error) {
|
||||||
|
// Do any custom processing here before marshalling
|
||||||
|
return []byte(string(c)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CIDRS represents each network entry in the YAML
|
||||||
|
type CIDRS struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
CIDR CIDR `yaml:"cidr"`
|
||||||
|
Except []CIDR `yaml:"except,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config represents the top-level YAML structure
|
||||||
|
type Config struct {
|
||||||
|
Networks []CIDRS `yaml:"networks"`
|
||||||
|
}
|
50
cmd/policygen/main.go
Normal file
50
cmd/policygen/main.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func execute(files []string, options *Options) error {
|
||||||
|
if len(files) != 1 {
|
||||||
|
return fmt.Errorf("File expected")
|
||||||
|
}
|
||||||
|
yamlFile, err := os.ReadFile(files[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading YAML file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the YAML content
|
||||||
|
dec := yaml.NewDecoder(bytes.NewReader(yamlFile),
|
||||||
|
yaml.UseJSONUnmarshaler(),
|
||||||
|
yaml.DisallowUnknownField(),
|
||||||
|
)
|
||||||
|
var config Config
|
||||||
|
err = dec.Decode(&config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error parsing YAML: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("PARSED %+v\n", config)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
options := Options{}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "policygen",
|
||||||
|
Short: "Generate network policies",
|
||||||
|
Long: "Generated policies based on a more compact representation of topology",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return execute(args, &options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
94
cmd/policygen/templates.go
Normal file
94
cmd/policygen/templates.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
import "embed"
|
||||||
|
import sprig "github.com/Masterminds/sprig/v3" // This provides most Helm functions
|
||||||
|
import "github.com/goccy/go-yaml"
|
||||||
|
|
||||||
|
// This provides most Helm functions
|
||||||
|
|
||||||
|
//go:embed templates/*
|
||||||
|
var templateFS embed.FS
|
||||||
|
|
||||||
|
func NewTemplate() *template.Template {
|
||||||
|
tmpl := template.New("")
|
||||||
|
|
||||||
|
// Combine sprig functions with custom functions
|
||||||
|
funcMap := template.FuncMap{}
|
||||||
|
|
||||||
|
// Add all sprig functions
|
||||||
|
for name, fn := range sprig.FuncMap() {
|
||||||
|
funcMap[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any custom functions you want
|
||||||
|
customFuncs := template.FuncMap{
|
||||||
|
"toYaml": func(v interface{}) string {
|
||||||
|
data, err := yaml.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge custom functions
|
||||||
|
for name, fn := range customFuncs {
|
||||||
|
funcMap[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the function map to the template
|
||||||
|
tmpl = tmpl.Funcs(funcMap)
|
||||||
|
|
||||||
|
return tmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func showContents(files fs.FS) {
|
||||||
|
entries, err := fs.ReadDir(files, ".")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
fmt.Printf("entry %s %s\n", entry.Name(), entry.Type())
|
||||||
|
if entry.Type().IsDir() {
|
||||||
|
subdir, err := fs.Sub(files, entry.Name())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
showContents(subdir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplates() (*template.Template, error) {
|
||||||
|
showContents(templateFS)
|
||||||
|
|
||||||
|
// Parse all templates at once from the embedded FS
|
||||||
|
tmpl := NewTemplate()
|
||||||
|
|
||||||
|
err := loadTemplatesGlob(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tmpl, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplatesGlob(tmpl *template.Template) error {
|
||||||
|
return fs.WalkDir(templateFS, ".", func(path string, d os.DirEntry, err error) error {
|
||||||
|
if strings.HasSuffix(path, ".yaml") {
|
||||||
|
data, err := fs.ReadFile(templateFS, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Loading template %s\n", path)
|
||||||
|
tmpl.New(path).Parse(string(data))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
15
cmd/policygen/templates/netpol/apiserver/cilium/egress.yaml
Normal file
15
cmd/policygen/templates/netpol/apiserver/cilium/egress.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
metadata:
|
||||||
|
name: {{.name}}
|
||||||
|
namespace: {{.namespace}}
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
{{ .selector }}
|
||||||
|
egress:
|
||||||
|
- toEntities:
|
||||||
|
- kube-apiserver
|
||||||
|
- toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: "6443"
|
||||||
|
protocol: TCP
|
13
cmd/policygen/templates/netpol/apiserver/cilium/ingress.yaml
Normal file
13
cmd/policygen/templates/netpol/apiserver/cilium/ingress.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
metadata:
|
||||||
|
name: {{.name}}
|
||||||
|
namespace: {{.namespace}}
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
{{ .selector }}
|
||||||
|
ingress:
|
||||||
|
- fromEntities:
|
||||||
|
- kube-apiserver
|
||||||
|
# See https://github.com/cilium/cilium/issues/35401
|
||||||
|
- remote-node
|
14
cmd/policygen/templates/netpol/egress.yaml
Normal file
14
cmd/policygen/templates/netpol/egress.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
kind: NetworkPolicy
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: "{{.name}}"
|
||||||
|
namespace: "{{.namespace}}"
|
||||||
|
spec:
|
||||||
|
policyTypes:
|
||||||
|
- Egress
|
||||||
|
podSelector:
|
||||||
|
{{.selector}}
|
||||||
|
egress:
|
||||||
|
{{- range $from := .from }}
|
||||||
|
- {{ $from | nindent 4 }}
|
||||||
|
{{- end }}
|
14
cmd/policygen/templates/netpol/ingress.yaml
Normal file
14
cmd/policygen/templates/netpol/ingress.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
kind: NetworkPolicy
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: "{{.name}}"
|
||||||
|
namespace: "{{.namespace}}"
|
||||||
|
spec:
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
podSelector:
|
||||||
|
{{.selector}}
|
||||||
|
ingress:
|
||||||
|
{{- range $from := .from }}
|
||||||
|
- {{ $from | nindent 4 }}
|
||||||
|
{{- end }}
|
12
example/config.yaml
Normal file
12
example/config.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- name: internet
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
except:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
- 172.16.0.0/12
|
||||||
|
- 192.168.0.0/16
|
||||||
|
|
||||||
|
|
21
go.mod
Normal file
21
go.mod
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module git.wamblee.org/public/policy-generator
|
||||||
|
|
||||||
|
go 1.23.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
|
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||||
|
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.15.13 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
|
github.com/spf13/cast v1.7.0 // indirect
|
||||||
|
github.com/spf13/cobra v1.8.1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
|
)
|
34
go.sum
Normal file
34
go.sum
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
|
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||||
|
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
|
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||||
|
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg=
|
||||||
|
github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||||
|
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||||
|
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
Loading…
Reference in New Issue
Block a user