Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • id/keystore
1 result
Show changes
Commits on Source (2)
Showing
with 2360 additions and 502 deletions
...@@ -3,7 +3,7 @@ package backend ...@@ -3,7 +3,7 @@ package backend
import ( import (
"context" "context"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// Database represents the interface to the underlying backend for // Database represents the interface to the underlying backend for
...@@ -15,15 +15,6 @@ type Database interface { ...@@ -15,15 +15,6 @@ type Database interface {
// Config is how users configure a database backend. // Config is how users configure a database backend.
type Config struct { type Config struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Params yaml.MapSlice `yaml:"params"` Params *yaml.Node `yaml:"params"`
}
// UnmarshalMapSlice re-unmarshals a partially-parsed yaml.MapSlice.
func UnmarshalMapSlice(raw yaml.MapSlice, obj interface{}) error {
b, err := yaml.Marshal(raw)
if err != nil {
return err
}
return yaml.Unmarshal(b, obj)
} }
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
ldaputil "git.autistici.org/ai3/go-common/ldap" ldaputil "git.autistici.org/ai3/go-common/ldap"
ct "git.autistici.org/ai3/go-common/ldap/compositetypes" ct "git.autistici.org/ai3/go-common/ldap/compositetypes"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"git.autistici.org/id/keystore/backend" "git.autistici.org/id/keystore/backend"
) )
...@@ -110,9 +110,9 @@ type ldapBackend struct { ...@@ -110,9 +110,9 @@ type ldapBackend struct {
} }
// New returns a new LDAP backend. // New returns a new LDAP backend.
func New(params yaml.MapSlice) (backend.Database, error) { func New(params *yaml.Node) (backend.Database, error) {
var config ldapConfig var config ldapConfig
if err := backend.UnmarshalMapSlice(params, &config); err != nil { if err := params.Decode(&config); err != nil {
return nil, err return nil, err
} }
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq" _ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"git.autistici.org/id/keystore/backend" "git.autistici.org/id/keystore/backend"
) )
...@@ -48,9 +48,9 @@ type sqlBackend struct { ...@@ -48,9 +48,9 @@ type sqlBackend struct {
} }
// New returns a new SQL backend. // New returns a new SQL backend.
func New(params yaml.MapSlice) (backend.Database, error) { func New(params *yaml.Node) (backend.Database, error) {
var config sqlConfig var config sqlConfig
if err := backend.UnmarshalMapSlice(params, &config); err != nil { if err := params.Decode(&config); err != nil {
return nil, err return nil, err
} }
if err := config.Valid(); err != nil { if err := config.Valid(); err != nil {
......
...@@ -2,7 +2,6 @@ package main ...@@ -2,7 +2,6 @@ package main
import ( import (
"flag" "flag"
"io/ioutil"
"log" "log"
"os" "os"
"os/signal" "os/signal"
...@@ -11,7 +10,7 @@ import ( ...@@ -11,7 +10,7 @@ import (
"git.autistici.org/ai3/go-common/unix" "git.autistici.org/ai3/go-common/unix"
"github.com/coreos/go-systemd/v22/daemon" "github.com/coreos/go-systemd/v22/daemon"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"git.autistici.org/id/keystore/dovecot" "git.autistici.org/id/keystore/dovecot"
) )
...@@ -25,12 +24,13 @@ var ( ...@@ -25,12 +24,13 @@ var (
// Read YAML config. // Read YAML config.
func loadConfig() (*dovecot.Config, error) { func loadConfig() (*dovecot.Config, error) {
data, err := ioutil.ReadFile(*configFile) f, err := os.Open(*configFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close()
var config dovecot.Config var config dovecot.Config
if err := yaml.Unmarshal(data, &config); err != nil { if err := yaml.NewDecoder(f).Decode(&config); err != nil {
return nil, err return nil, err
} }
return &config, nil return &config, nil
......
...@@ -2,10 +2,10 @@ package main ...@@ -2,10 +2,10 @@ package main
import ( import (
"flag" "flag"
"io/ioutil"
"log" "log"
"os"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"git.autistici.org/ai3/go-common/serverutil" "git.autistici.org/ai3/go-common/serverutil"
...@@ -32,12 +32,13 @@ type Config struct { ...@@ -32,12 +32,13 @@ type Config struct {
func loadConfig() (*Config, error) { func loadConfig() (*Config, error) {
// Read YAML config. // Read YAML config.
data, err := ioutil.ReadFile(*configFile) f, err := os.Open(*configFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close()
var config Config var config Config
if err := yaml.Unmarshal(data, &config); err != nil { if err := yaml.NewDecoder(f).Decode(&config); err != nil {
return nil, err return nil, err
} }
return &config, nil return &config, nil
......
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
"git.autistici.org/ai3/go-common/userenckey" "git.autistici.org/ai3/go-common/userenckey"
"git.autistici.org/id/go-sso" "git.autistici.org/id/go-sso"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"git.autistici.org/id/keystore/backend" "git.autistici.org/id/keystore/backend"
keystore "git.autistici.org/id/keystore/server" keystore "git.autistici.org/id/keystore/server"
...@@ -44,12 +44,11 @@ func withTestDB(t testing.TB, schema string) (func(), string) { ...@@ -44,12 +44,11 @@ func withTestDB(t testing.TB, schema string) (func(), string) {
}, dbPath }, dbPath
} }
func makeMapSlice(m map[string]interface{}) yaml.MapSlice { func makeMapSlice(m map[string]interface{}) *yaml.Node {
var out yaml.MapSlice var out yaml.Node
for k, v := range m { d, _ := yaml.Marshal(m)
out = append(out, yaml.MapItem{Key: k, Value: v}) yaml.Unmarshal(d, &out)
} return &out
return out
} }
var ( var (
......
...@@ -3,14 +3,14 @@ module git.autistici.org/id/keystore ...@@ -3,14 +3,14 @@ module git.autistici.org/id/keystore
go 1.15 go 1.15
require ( require (
git.autistici.org/ai3/go-common v0.0.0-20210308183328-6c663e9176af git.autistici.org/ai3/go-common v0.0.0-20220912095004-9a984189694c
git.autistici.org/id/go-sso v0.0.0-20210308195111-62a3d97a1dda git.autistici.org/id/go-sso v0.0.0-20210308195111-62a3d97a1dda
github.com/coreos/go-systemd/v22 v22.2.0 github.com/coreos/go-systemd/v22 v22.3.2
github.com/go-ldap/ldap/v3 v3.2.4 github.com/go-ldap/ldap/v3 v3.4.4
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.6.0
github.com/lib/pq v1.10.0 github.com/lib/pq v1.10.1
github.com/mattn/go-sqlite3 v1.14.6 github.com/mattn/go-sqlite3 v1.14.15
github.com/prometheus/client_golang v1.9.0 github.com/prometheus/client_golang v1.12.2
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1
) )
This diff is collapsed.
...@@ -199,4 +199,4 @@ ...@@ -199,4 +199,4 @@
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
\ No newline at end of file
// Copyright 2014 Google LLC
//
// 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 metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "cloud.google.com/go/compute/metadata"
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"sync"
"time"
)
const (
// metadataIP is the documented metadata server IP address.
metadataIP = "169.254.169.254"
// metadataHostEnv is the environment variable specifying the
// GCE metadata hostname. If empty, the default value of
// metadataIP ("169.254.169.254") is used instead.
// This is variable name is not defined by any spec, as far as
// I know; it was made up for the Go package.
metadataHostEnv = "GCE_METADATA_HOST"
userAgent = "gcloud-golang/0.1"
)
type cachedValue struct {
k string
trim bool
mu sync.Mutex
v string
}
var (
projID = &cachedValue{k: "project/project-id", trim: true}
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
instID = &cachedValue{k: "instance/id", trim: true}
)
var defaultClient = &Client{hc: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}}
// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string
func (suffix NotDefinedError) Error() string {
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}
func (c *cachedValue) get(cl *Client) (v string, err error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.v != "" {
return c.v, nil
}
if c.trim {
v, err = cl.getTrimmed(c.k)
} else {
v, err = cl.Get(c.k)
}
if err == nil {
c.v = v
}
return
}
var (
onGCEOnce sync.Once
onGCE bool
)
// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
onGCEOnce.Do(initOnGCE)
return onGCE
}
func initOnGCE() {
onGCE = testOnGCE()
}
func testOnGCE() bool {
// The user explicitly said they're on GCE, so trust them.
if os.Getenv(metadataHostEnv) != "" {
return true
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resc := make(chan bool, 2)
// Try two strategies in parallel.
// See https://github.com/googleapis/google-cloud-go/issues/194
go func() {
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
req.Header.Set("User-Agent", userAgent)
res, err := defaultClient.hc.Do(req.WithContext(ctx))
if err != nil {
resc <- false
return
}
defer res.Body.Close()
resc <- res.Header.Get("Metadata-Flavor") == "Google"
}()
go func() {
addrs, err := net.DefaultResolver.LookupHost(ctx, "metadata.google.internal")
if err != nil || len(addrs) == 0 {
resc <- false
return
}
resc <- strsContains(addrs, metadataIP)
}()
tryHarder := systemInfoSuggestsGCE()
if tryHarder {
res := <-resc
if res {
// The first strategy succeeded, so let's use it.
return true
}
// Wait for either the DNS or metadata server probe to
// contradict the other one and say we are running on
// GCE. Give it a lot of time to do so, since the system
// info already suggests we're running on a GCE BIOS.
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
select {
case res = <-resc:
return res
case <-timer.C:
// Too slow. Who knows what this system is.
return false
}
}
// There's no hint from the system info that we're running on
// GCE, so use the first probe's result as truth, whether it's
// true or false. The goal here is to optimize for speed for
// users who are NOT running on GCE. We can't assume that
// either a DNS lookup or an HTTP request to a blackholed IP
// address is fast. Worst case this should return when the
// metaClient's Transport.ResponseHeaderTimeout or
// Transport.Dial.Timeout fires (in two seconds).
return <-resc
}
// systemInfoSuggestsGCE reports whether the local system (without
// doing network requests) suggests that we're running on GCE. If this
// returns true, testOnGCE tries a bit harder to reach its metadata
// server.
func systemInfoSuggestsGCE() bool {
if runtime.GOOS != "linux" {
// We don't have any non-Linux clues available, at least yet.
return false
}
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
name := strings.TrimSpace(string(slurp))
return name == "Google" || name == "Google Compute Engine"
}
// Subscribe calls Client.Subscribe on the default client.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
return defaultClient.Subscribe(suffix, fn)
}
// Get calls Client.Get on the default client.
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return defaultClient.ProjectID() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) { return defaultClient.InternalIP() }
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
// Email calls Client.Email on the default client.
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) { return defaultClient.Hostname() }
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) { return defaultClient.InstanceID() }
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) { return defaultClient.InstanceName() }
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) { return defaultClient.Zone() }
// InstanceAttributes calls Client.InstanceAttributes on the default client.
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
// ProjectAttributes calls Client.ProjectAttributes on the default client.
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
func InstanceAttributeValue(attr string) (string, error) {
return defaultClient.InstanceAttributeValue(attr)
}
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
func ProjectAttributeValue(attr string) (string, error) {
return defaultClient.ProjectAttributeValue(attr)
}
// Scopes calls Client.Scopes on the default client.
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
func strsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// A Client provides metadata.
type Client struct {
hc *http.Client
}
// NewClient returns a Client that can be used to fetch metadata.
// Returns the client that uses the specified http.Client for HTTP requests.
// If nil is specified, returns the default client.
func NewClient(c *http.Client) *Client {
if c == nil {
return defaultClient
}
return &Client{hc: c}
}
// getETag returns a value from the metadata service as well as the associated ETag.
// This func is otherwise equivalent to Get.
func (c *Client) getETag(suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv(metadataHostEnv)
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = metadataIP
}
suffix = strings.TrimLeft(suffix, "/")
u := "http://" + host + "/computeMetadata/v1/" + suffix
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return "", "", err
}
req.Header.Set("Metadata-Flavor", "Google")
req.Header.Set("User-Agent", userAgent)
res, err := c.hc.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
if res.StatusCode != 200 {
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
}
return string(all), res.Header.Get("Etag"), nil
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func (c *Client) Get(suffix string) (string, error) {
val, _, err := c.getETag(suffix)
return val, err
}
func (c *Client) getTrimmed(suffix string) (s string, err error) {
s, err = c.Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *Client) lines(suffix string) ([]string, error) {
j, err := c.Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// ProjectID returns the current instance's project ID string.
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
// NumericProjectID returns the current instance's numeric project ID.
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
// InstanceID returns the current VM's numeric instance ID.
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
// InternalIP returns the instance's primary internal IP address.
func (c *Client) InternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/ip")
}
// Email returns the email address associated with the service account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Email(serviceAccount string) (string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
}
// ExternalIP returns the instance's primary external (public) IP address.
func (c *Client) ExternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func (c *Client) Hostname() (string, error) {
return c.getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func (c *Client) InstanceTags() ([]string, error) {
var s []string
j, err := c.Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceName returns the current VM's instance ID string.
func (c *Client) InstanceName() (string, error) {
return c.getTrimmed("instance/name")
}
// Zone returns the current VM's zone, such as "us-central1-b".
func (c *Client) Zone() (string, error) {
zone, err := c.getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
return c.Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
return c.Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
}
// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
const failedSubscribeSleep = time.Second * 5
// First check to see if the metadata value exists at all.
val, lastETag, err := c.getETag(suffix)
if err != nil {
return err
}
if err := fn(val, true); err != nil {
return err
}
ok := true
if strings.ContainsRune(suffix, '?') {
suffix += "&wait_for_change=true&last_etag="
} else {
suffix += "?wait_for_change=true&last_etag="
}
for {
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
if err != nil {
if _, deleted := err.(NotDefinedError); !deleted {
time.Sleep(failedSubscribeSleep)
continue // Retry on other errors.
}
ok = false
}
lastETag = etag
if err := fn(val, ok); err != nil || !ok {
return err
}
}
}
// Error contains an error response from the server.
type Error struct {
// Code is the HTTP response status code.
Code int
// Message is the server response message.
Message string
}
func (e *Error) Error() string {
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
}
language: go
go_import_path: contrib.go.opencensus.io
go:
- 1.11.x
env:
global:
GO111MODULE=on
before_script:
- make install-tools
script:
- make travis-ci
# TODO: Fix this on windows.
ALL_SRC := $(shell find . -name '*.go' \
-not -path './vendor/*' \
-not -path '*/gen-go/*' \
-type f | sort)
ALL_PKGS := $(shell go list $(sort $(dir $(ALL_SRC))))
GOTEST_OPT?=-v -race -timeout 30s
GOTEST_OPT_WITH_COVERAGE = $(GOTEST_OPT) -coverprofile=coverage.txt -covermode=atomic
GOTEST=go test
GOFMT=gofmt
GOLINT=golint
GOVET=go vet
EMBEDMD=embedmd
# TODO decide if we need to change these names.
README_FILES := $(shell find . -name '*README.md' | sort | tr '\n' ' ')
.DEFAULT_GOAL := fmt-lint-vet-embedmd-test
.PHONY: fmt-lint-vet-embedmd-test
fmt-lint-vet-embedmd-test: fmt lint vet embedmd test
# TODO enable test-with-coverage in tavis
.PHONY: travis-ci
travis-ci: fmt lint vet embedmd test test-386
all-pkgs:
@echo $(ALL_PKGS) | tr ' ' '\n' | sort
all-srcs:
@echo $(ALL_SRC) | tr ' ' '\n' | sort
.PHONY: test
test:
$(GOTEST) $(GOTEST_OPT) $(ALL_PKGS)
.PHONY: test-386
test-386:
GOARCH=386 $(GOTEST) -v -timeout 30s $(ALL_PKGS)
.PHONY: test-with-coverage
test-with-coverage:
$(GOTEST) $(GOTEST_OPT_WITH_COVERAGE) $(ALL_PKGS)
.PHONY: fmt
fmt:
@FMTOUT=`$(GOFMT) -s -l $(ALL_SRC) 2>&1`; \
if [ "$$FMTOUT" ]; then \
echo "$(GOFMT) FAILED => gofmt the following files:\n"; \
echo "$$FMTOUT\n"; \
exit 1; \
else \
echo "Fmt finished successfully"; \
fi
.PHONY: lint
lint:
@LINTOUT=`$(GOLINT) $(ALL_PKGS) 2>&1`; \
if [ "$$LINTOUT" ]; then \
echo "$(GOLINT) FAILED => clean the following lint errors:\n"; \
echo "$$LINTOUT\n"; \
exit 1; \
else \
echo "Lint finished successfully"; \
fi
.PHONY: vet
vet:
# TODO: Understand why go vet downloads "github.com/google/go-cmp v0.2.0"
@VETOUT=`$(GOVET) ./... | grep -v "go: downloading" 2>&1`; \
if [ "$$VETOUT" ]; then \
echo "$(GOVET) FAILED => go vet the following files:\n"; \
echo "$$VETOUT\n"; \
exit 1; \
else \
echo "Vet finished successfully"; \
fi
.PHONY: embedmd
embedmd:
@EMBEDMDOUT=`$(EMBEDMD) -d $(README_FILES) 2>&1`; \
if [ "$$EMBEDMDOUT" ]; then \
echo "$(EMBEDMD) FAILED => embedmd the following files:\n"; \
echo "$$EMBEDMDOUT\n"; \
exit 1; \
else \
echo "Embedmd finished successfully"; \
fi
.PHONY: install-tools
install-tools:
go get -u golang.org/x/tools/cmd/cover
go get -u golang.org/x/lint/golint
go get -u github.com/rakyll/embedmd
# OpenCensus Go Zipkin Exporter
[![Build Status](https://travis-ci.org/census-ecosystem/opencensus-go-exporter-zipkin.svg?branch=master)](https://travis-ci.org/census-ecosystem/opencensus-go-exporter-zipkin) [![GoDoc][godoc-image]][godoc-url]
Provides OpenCensus exporter support for Zipkin.
## Installation
```
$ go get -u contrib.go.opencensus.io/exporter/zipkin
```
[godoc-image]: https://godoc.org/contrib.go.opencensus.io/exporter/zipkin?status.svg
[godoc-url]: https://godoc.org/contrib.go.opencensus.io/exporter/zipkin
module contrib.go.opencensus.io/exporter/zipkin
require (
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/openzipkin/zipkin-go v0.2.2
go.opencensus.io v0.22.4
)
// Copyright 2017, OpenCensus Authors
//
// 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 zipkin contains an trace exporter for Zipkin.
package zipkin // import "contrib.go.opencensus.io/exporter/zipkin"
import (
"encoding/binary"
"fmt"
"strconv"
"github.com/openzipkin/zipkin-go/model"
"github.com/openzipkin/zipkin-go/reporter"
"go.opencensus.io/trace"
)
// Exporter is an implementation of trace.Exporter that uploads spans to a
// Zipkin server.
type Exporter struct {
reporter reporter.Reporter
localEndpoint *model.Endpoint
}
// NewExporter returns an implementation of trace.Exporter that uploads spans
// to a Zipkin server.
//
// reporter is a Zipkin Reporter which will be used to send the spans. These
// can be created with the openzipkin library, using one of the packages under
// github.com/openzipkin/zipkin-go/reporter.
//
// localEndpoint sets the local endpoint of exported spans. It can be
// constructed with github.com/openzipkin/zipkin-go.NewEndpoint, e.g.:
// localEndpoint, err := NewEndpoint("my server", listener.Addr().String())
// localEndpoint can be nil.
func NewExporter(reporter reporter.Reporter, localEndpoint *model.Endpoint) *Exporter {
return &Exporter{
reporter: reporter,
localEndpoint: localEndpoint,
}
}
// ExportSpan exports a span to a Zipkin server.
func (e *Exporter) ExportSpan(s *trace.SpanData) {
e.reporter.Send(zipkinSpan(s, e.localEndpoint))
}
const (
statusCodeTagKey = "error"
statusDescriptionTagKey = "opencensus.status_description"
)
var (
sampledTrue = true
canonicalCodes = [...]string{
"OK",
"CANCELLED",
"UNKNOWN",
"INVALID_ARGUMENT",
"DEADLINE_EXCEEDED",
"NOT_FOUND",
"ALREADY_EXISTS",
"PERMISSION_DENIED",
"RESOURCE_EXHAUSTED",
"FAILED_PRECONDITION",
"ABORTED",
"OUT_OF_RANGE",
"UNIMPLEMENTED",
"INTERNAL",
"UNAVAILABLE",
"DATA_LOSS",
"UNAUTHENTICATED",
}
)
func canonicalCodeString(code int32) string {
if code < 0 || int(code) >= len(canonicalCodes) {
return "error code " + strconv.FormatInt(int64(code), 10)
}
return canonicalCodes[code]
}
func convertTraceID(t trace.TraceID) model.TraceID {
return model.TraceID{
High: binary.BigEndian.Uint64(t[:8]),
Low: binary.BigEndian.Uint64(t[8:]),
}
}
func convertSpanID(s trace.SpanID) model.ID {
return model.ID(binary.BigEndian.Uint64(s[:]))
}
func spanKind(s *trace.SpanData) model.Kind {
switch s.SpanKind {
case trace.SpanKindClient:
return model.Client
case trace.SpanKindServer:
return model.Server
}
return model.Undetermined
}
func zipkinSpan(s *trace.SpanData, localEndpoint *model.Endpoint) model.SpanModel {
sc := s.SpanContext
z := model.SpanModel{
SpanContext: model.SpanContext{
TraceID: convertTraceID(sc.TraceID),
ID: convertSpanID(sc.SpanID),
Sampled: &sampledTrue,
},
Kind: spanKind(s),
Name: s.Name,
Timestamp: s.StartTime,
Shared: false,
LocalEndpoint: localEndpoint,
}
if s.ParentSpanID != (trace.SpanID{}) {
id := convertSpanID(s.ParentSpanID)
z.ParentID = &id
}
if s, e := s.StartTime, s.EndTime; !s.IsZero() && !e.IsZero() {
z.Duration = e.Sub(s)
}
// construct Tags from s.Attributes and s.Status.
if len(s.Attributes) != 0 {
m := make(map[string]string, len(s.Attributes)+2)
for key, value := range s.Attributes {
switch v := value.(type) {
case string:
m[key] = v
case bool:
if v {
m[key] = "true"
} else {
m[key] = "false"
}
case int64:
m[key] = strconv.FormatInt(v, 10)
case float64:
m[key] = strconv.FormatFloat(v, 'f', -1, 64)
}
}
z.Tags = m
}
if s.Status.Code != 0 || s.Status.Message != "" {
if z.Tags == nil {
z.Tags = make(map[string]string, 2)
}
if s.Status.Code != 0 {
z.Tags[statusCodeTagKey] = canonicalCodeString(s.Status.Code)
}
if s.Status.Message != "" {
z.Tags[statusDescriptionTagKey] = s.Status.Message
}
}
// construct Annotations from s.Annotations and s.MessageEvents.
if len(s.Annotations) != 0 || len(s.MessageEvents) != 0 {
z.Annotations = make([]model.Annotation, 0, len(s.Annotations)+len(s.MessageEvents))
for _, a := range s.Annotations {
z.Annotations = append(z.Annotations, model.Annotation{
Timestamp: a.Time,
Value: a.Message,
})
}
for _, m := range s.MessageEvents {
a := model.Annotation{
Timestamp: m.Time,
}
switch m.EventType {
case trace.MessageEventTypeSent:
a.Value = fmt.Sprintf("Sent %d bytes", m.UncompressedByteSize)
case trace.MessageEventTypeRecv:
a.Value = fmt.Sprintf("Received %d bytes", m.UncompressedByteSize)
default:
a.Value = "<?>"
}
z.Annotations = append(z.Annotations, a)
}
}
return z
}
...@@ -9,6 +9,8 @@ run_tests: ...@@ -9,6 +9,8 @@ run_tests:
artifacts: artifacts:
when: always when: always
reports: reports:
cobertura: cover.xml coverage_report:
coverage_format: cobertura
path: cover.xml
junit: report.xml junit: report.xml
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"mime"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
...@@ -158,7 +159,7 @@ func (b *balancedBackend) Call(ctx context.Context, shard, path string, req, res ...@@ -158,7 +159,7 @@ func (b *balancedBackend) Call(ctx context.Context, shard, path string, req, res
defer httpResp.Body.Close() // nolint defer httpResp.Body.Close() // nolint
// Decode the response, unless the 'resp' output is nil. // Decode the response, unless the 'resp' output is nil.
if httpResp.Header.Get("Content-Type") != "application/json" { if ct, _, _ := mime.ParseMediaType(httpResp.Header.Get("Content-Type")); ct != "application/json" {
return errors.New("not a JSON response") return errors.New("not a JSON response")
} }
if resp == nil { if resp == nil {
......
module git.autistici.org/ai3/go-common module git.autistici.org/ai3/go-common
go 1.11 go 1.14
require ( require (
contrib.go.opencensus.io/exporter/zipkin v0.1.2 github.com/NYTimes/gziphandler v1.1.1
github.com/amoghe/go-crypt v0.0.0-20191109212615-b2ff80594b7f github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
github.com/bbrks/wrap/v2 v2.5.0 github.com/bbrks/wrap/v2 v2.5.0
github.com/cenkalti/backoff/v4 v4.1.0 github.com/cenkalti/backoff/v4 v4.1.3
github.com/coreos/go-systemd/v22 v22.2.0 github.com/coreos/go-systemd/v22 v22.3.2
github.com/duo-labs/webauthn v0.0.0-20220330035159-03696f3d4499
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594
github.com/go-asn1-ber/asn1-ber v1.5.3 github.com/fxamacker/cbor/v2 v2.4.0
github.com/go-ldap/ldap/v3 v3.2.4 github.com/go-asn1-ber/asn1-ber v1.5.4
github.com/go-ldap/ldap/v3 v3.4.4
github.com/gofrs/flock v0.8.0 // indirect github.com/gofrs/flock v0.8.0 // indirect
github.com/google/go-cmp v0.5.5 github.com/google/go-cmp v0.5.9
github.com/gorilla/handlers v1.5.1
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mattn/go-sqlite3 v1.14.15
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/prometheus/client_golang v1.12.2
github.com/openzipkin/zipkin-go v0.2.5
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/prometheus/client_golang v1.9.0
github.com/russross/blackfriday/v2 v2.1.0 github.com/russross/blackfriday/v2 v2.1.0
github.com/theckman/go-flock v0.8.0 github.com/theckman/go-flock v0.8.1
github.com/tstranex/u2f v1.0.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0
go.opencensus.io v0.23.0 go.opentelemetry.io/contrib/propagators/b3 v1.9.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 go.opentelemetry.io/otel v1.9.0
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 // indirect go.opentelemetry.io/otel/exporters/zipkin v1.9.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c go.opentelemetry.io/otel/sdk v1.9.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect go.opentelemetry.io/otel/trace v1.9.0
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/sync v0.0.0-20220907140024-f12130a52804
) )
...@@ -16,11 +16,16 @@ ...@@ -16,11 +16,16 @@
package compositetypes package compositetypes
import ( import (
"crypto/elliptic" "encoding/base64"
"encoding/hex"
"encoding/json"
"errors" "errors"
"fmt"
"strings" "strings"
"github.com/tstranex/u2f" "github.com/duo-labs/webauthn/protocol/webauthncose"
"github.com/duo-labs/webauthn/webauthn"
"github.com/fxamacker/cbor/v2"
) )
// AppSpecificPassword stores information on an application-specific // AppSpecificPassword stores information on an application-specific
...@@ -98,62 +103,152 @@ func UnmarshalEncryptedKey(s string) (*EncryptedKey, error) { ...@@ -98,62 +103,152 @@ func UnmarshalEncryptedKey(s string) (*EncryptedKey, error) {
}, nil }, nil
} }
// U2FRegistration stores information on a single U2F device // U2FRegistration stores information on a single WebAuthN/U2F device
// registration. // registration.
// //
// The serialized format follows part of the U2F standard and just // The public key is expected to be in raw COSE format. Note that on
// stores 64 bytes of the public key immediately followed by the key // the wire (i.e. when serialized as JSON) both the public key and the
// handle data, with no encoding. Note that the public key itself is a // key handle are base64-encoded.
// serialization of the elliptic curve parameters.
// //
// The data in U2FRegistration is still encoded, but it can be turned // It is possible to obtain a usable webauthn.Credential object at
// into a usable form (github.com/tstranex/u2f.Registration) later. // run-time by calling Decode().
type U2FRegistration struct { type U2FRegistration struct {
KeyHandle []byte `json:"key_handle"` KeyHandle []byte `json:"key_handle"`
PublicKey []byte `json:"public_key"` PublicKey []byte `json:"public_key"`
Comment string `json:"comment"`
Legacy bool `json:"-"`
} }
// Marshal returns the serialized format. // Marshal returns the serialized format.
func (r *U2FRegistration) Marshal() string { func (r *U2FRegistration) Marshal() string {
var b []byte data, err := json.Marshal(r)
b = append(b, r.PublicKey...) if err != nil {
b = append(b, r.KeyHandle...) panic(err)
return string(b) }
return string(data)
} }
const (
legacySerializedU2FKeySize = 65
minU2FKeySize = 64
)
// UnmarshalU2FRegistration parses a U2FRegistration from its serialized format. // UnmarshalU2FRegistration parses a U2FRegistration from its serialized format.
func UnmarshalU2FRegistration(s string) (*U2FRegistration, error) { func UnmarshalU2FRegistration(s string) (*U2FRegistration, error) {
if len(s) < 65 { // Try JSON first.
var reg U2FRegistration
if err := json.NewDecoder(strings.NewReader(s)).Decode(&reg); err == nil {
return &reg, nil
}
// Deserialize legacy format, and perform a conversion of the
// public key to COSE format.
if len(s) < legacySerializedU2FKeySize {
return nil, errors.New("badly encoded u2f registration") return nil, errors.New("badly encoded u2f registration")
} }
b := []byte(s) b := []byte(s)
return &U2FRegistration{ return &U2FRegistration{
PublicKey: b[:65], PublicKey: u2fToCOSE(b[:legacySerializedU2FKeySize]),
KeyHandle: b[65:], KeyHandle: b[legacySerializedU2FKeySize:],
Legacy: true,
}, nil }, nil
} }
// Decode returns a u2f.Registration object with the decoded public // ParseLegacyU2FRegistrationFromStrings parses the legacy U2F format used
// key ready for use in verification. // in manual key specifications etc. which consists of a
func (r *U2FRegistration) Decode() (*u2f.Registration, error) { // base64(url)-encoded key handle, and a hex-encoded public key (in
x, y := elliptic.Unmarshal(elliptic.P256(), r.PublicKey) // legacy U2F format).
if x == nil { func ParseLegacyU2FRegistrationFromStrings(keyHandle, publicKey string) (*U2FRegistration, error) {
return nil, errors.New("invalid public key") // U2F key handles are base64(url)-encoded (no trailing =s).
kh, err := base64.RawURLEncoding.DecodeString(keyHandle)
if err != nil {
return nil, fmt.Errorf("error decoding key handle: %w", err)
} }
var reg u2f.Registration
reg.PubKey.Curve = elliptic.P256() // U2F public keys are hex-encoded.
reg.PubKey.X = x pk, err := hex.DecodeString(publicKey)
reg.PubKey.Y = y if err != nil {
reg.KeyHandle = r.KeyHandle return nil, fmt.Errorf("error decoding public key: %w", err)
return &reg, nil }
// Simple sanity check for non-empty fields.
if len(kh) == 0 {
return nil, errors.New("missing key handle")
}
if len(pk) < minU2FKeySize {
return nil, errors.New("public key missing or too short")
}
return &U2FRegistration{
PublicKey: u2fToCOSE(pk),
KeyHandle: kh,
Legacy: true,
}, nil
} }
// NewU2FRegistrationFromData creates a U2FRegistration from a // ParseU2FRegistrationFromStrings parses the U2F registration format
// u2f.Registration object. // used in manual key specifications that is used by Fido2-aware
func NewU2FRegistrationFromData(reg *u2f.Registration) *U2FRegistration { // programs such as pamu2fcfg >= 1.0.0. Both parameters are
pk := elliptic.Marshal(reg.PubKey.Curve, reg.PubKey.X, reg.PubKey.Y) // base64-encoded, public key should be in COSE format.
func ParseU2FRegistrationFromStrings(keyHandle, publicKey string) (*U2FRegistration, error) {
kh, err := base64.StdEncoding.DecodeString(keyHandle)
if err != nil {
return nil, fmt.Errorf("error decoding key handle: %w", err)
}
pk, err := base64.StdEncoding.DecodeString(publicKey)
if err != nil {
return nil, fmt.Errorf("error decoding public key: %w", err)
}
// Simple sanity check for non-empty fields.
if len(kh) == 0 {
return nil, errors.New("missing key handle")
}
if len(pk) < minU2FKeySize {
return nil, errors.New("public key missing or too short")
}
return &U2FRegistration{ return &U2FRegistration{
PublicKey: pk, PublicKey: pk,
KeyHandle: reg.KeyHandle, KeyHandle: kh,
} }, nil
} }
// Decode returns a u2f.Registration object with the decoded public
// key ready for use in verification.
func (r *U2FRegistration) Decode() (webauthn.Credential, error) {
return webauthn.Credential{
ID: r.KeyHandle,
PublicKey: r.PublicKey,
}, nil
}
// Convert a legacy U2F public key to COSE format.
func u2fToCOSE(pk []byte) []byte {
var key webauthncose.EC2PublicKeyData
key.KeyType = int64(webauthncose.EllipticKey)
key.Algorithm = int64(webauthncose.AlgES256)
key.XCoord = pk[1:33]
key.YCoord = pk[33:]
data, _ := cbor.Marshal(&key) // nolint: errcheck
return data
}
// Faster, but more questionable, implementation of the above:
//
// func u2fToCOSE(pk []byte) []byte {
// x := pk[1:33]
// y := pk[33:]
// out := []byte{
// 0xa4,
// 0x01, 0x02,
// 0x03, 0x26,
// 0x21, 0x58, 0x20,
// }
// out = append(out, x...)
// out = append(out, []byte{
// 0x22, 0x58, 0x20,
// }...)
// out = append(out, y...)
// return out
// }