Big dependency update, all lowercase sirupsen's for all dependencies.

This commit is contained in:
Travis Reeder
2017-08-23 19:52:56 -07:00
parent f559acd7ed
commit d7bf64bf66
6149 changed files with 870816 additions and 184795 deletions

View File

@@ -21,7 +21,8 @@ jobs:
# mkdir -p $HOME/golang # mkdir -p $HOME/golang
# tar -C $HOME/golang -xzf go1.8.3.linux-amd64.tar.gz # tar -C $HOME/golang -xzf go1.8.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go$GOVERSION.$OS-$ARCH.tar.gz sudo tar -C /usr/local -xzf go$GOVERSION.$OS-$ARCH.tar.gz
go get -u github.com/golang/dep/... # go get -u github.com/golang/dep/...
# go get -u github.com/Masterminds/glide
- run: go version - run: go version
# update Docker # update Docker
- run: | - run: |

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
vendor/
_vendor*/

View File

@@ -2,7 +2,10 @@
FROM golang:alpine AS build-env FROM golang:alpine AS build-env
RUN apk --no-cache add build-base git bzr mercurial gcc RUN apk --no-cache add build-base git bzr mercurial gcc
ENV D=/go/src/github.com/fnproject/fn ENV D=/go/src/github.com/fnproject/fn
# TODO: once we get rid of the vendor dirs, add dep step using `dep ensure --vendor-only` from here: https://medium.com/travis-on-docker/triple-stage-docker-builds-with-go-and-angular-1b7d2006cb88 # If dep ever gets decent enough to use, try `dep ensure --vendor-only` from here: https://medium.com/travis-on-docker/triple-stage-docker-builds-with-go-and-angular-1b7d2006cb88
RUN go get -u github.com/Masterminds/glide
ADD glide.* $D/
RUN cd $D && glide install -v
ADD . $D ADD . $D
RUN cd $D && go build -o fn-alpine && cp fn-alpine /tmp/ RUN cd $D && go build -o fn-alpine && cp fn-alpine /tmp/

View File

@@ -4,10 +4,10 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"github.com/Sirupsen/logrus"
"github.com/fnproject/fn/api/datastore/internal/datastoreutil" "github.com/fnproject/fn/api/datastore/internal/datastoreutil"
"github.com/fnproject/fn/api/datastore/sql" "github.com/fnproject/fn/api/datastore/sql"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/sirupsen/logrus"
) )
func New(dbURL string) (models.Datastore, error) { func New(dbURL string) (models.Datastore, error) {

View File

@@ -13,7 +13,7 @@ import (
"reflect" "reflect"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
) )

View File

@@ -12,7 +12,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/datastore/sql" "github.com/fnproject/fn/api/datastore/sql"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
) )

View File

@@ -11,7 +11,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
mq_config "github.com/iron-io/iron_go3/config" mq_config "github.com/iron-io/iron_go3/config"
ironmq "github.com/iron-io/iron_go3/mq" ironmq "github.com/iron-io/iron_go3/mq"

View File

@@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
"github.com/google/btree" "github.com/google/btree"

View File

@@ -6,7 +6,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
) )

View File

@@ -9,7 +9,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
"github.com/garyburd/redigo/redis" "github.com/garyburd/redigo/redis"

View File

@@ -15,7 +15,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
taskpkg "github.com/fnproject/fn/api/runner/task" taskpkg "github.com/fnproject/fn/api/runner/task"

View File

@@ -13,7 +13,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/datastore" "github.com/fnproject/fn/api/datastore"
"github.com/fnproject/fn/api/logs" "github.com/fnproject/fn/api/logs"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"

View File

@@ -3,7 +3,7 @@ package common
import ( import (
"context" "context"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// WithLogger stores the logger. // WithLogger stores the logger.

View File

@@ -4,7 +4,7 @@ import (
"net/url" "net/url"
"os" "os"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func SetLogLevel(ll string) { func SetLogLevel(ll string) {

View File

@@ -3,7 +3,7 @@ package stats
import ( import (
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type LogReporter struct { type LogReporter struct {

View File

@@ -8,7 +8,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type NewRelicAgentConfig struct { type NewRelicAgentConfig struct {

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/amir/raidman" "github.com/amir/raidman"
) )

View File

@@ -5,7 +5,7 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func postStatHat(key, stat string, values url.Values) { func postStatHat(key, stat string, values url.Values) {

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type HTTPSubHandler interface { type HTTPSubHandler interface {

View File

@@ -6,8 +6,8 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/syslog" logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
) )
func NewSyslogHook(url *url.URL, prefix string) error { func NewSyslogHook(url *url.URL, prefix string) error {

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
"github.com/fnproject/fn/api/runner/drivers" "github.com/fnproject/fn/api/runner/drivers"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"

View File

@@ -12,7 +12,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"

View File

@@ -8,7 +8,7 @@ import (
"io" "io"
"sync" "sync"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
) )

View File

@@ -13,7 +13,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
"github.com/fnproject/fn/api/runner/drivers" "github.com/fnproject/fn/api/runner/drivers"

View File

@@ -9,7 +9,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/id" "github.com/fnproject/fn/api/id"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/drivers" "github.com/fnproject/fn/api/runner/drivers"

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/datastore" "github.com/fnproject/fn/api/datastore"
"github.com/fnproject/fn/api/logs" "github.com/fnproject/fn/api/logs"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"

View File

@@ -7,7 +7,7 @@ import (
"net/http" "net/http"
"runtime/debug" "runtime/debug"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/runner/common" "github.com/fnproject/fn/api/runner/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"

View File

@@ -7,7 +7,7 @@ import (
"os/signal" "os/signal"
"strings" "strings"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/spf13/viper" "github.com/spf13/viper"
) )

View File

@@ -11,7 +11,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/fnproject/fn/api" "github.com/fnproject/fn/api"
"github.com/fnproject/fn/api/id" "github.com/fnproject/fn/api/id"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"

View File

@@ -13,7 +13,7 @@ import (
"path" "path"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/ccirello/supervisor" "github.com/ccirello/supervisor"
"github.com/fnproject/fn/api" "github.com/fnproject/fn/api"
"github.com/fnproject/fn/api/datastore" "github.com/fnproject/fn/api/datastore"

2
cli/glide.lock generated
View File

@@ -129,7 +129,7 @@ imports:
version: b938d81255b5473c57635324295cb0fe398c7a58 version: b938d81255b5473c57635324295cb0fe398c7a58
- name: github.com/PuerkitoBio/urlesc - name: github.com/PuerkitoBio/urlesc
version: bbf7a2afc14f93e1e0a5c06df524fbd75e5031e5 version: bbf7a2afc14f93e1e0a5c06df524fbd75e5031e5
- name: github.com/Sirupsen/logrus - name: github.com/sirupsen/logrus
version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
repo: https://github.com/sirupsen/logrus repo: https://github.com/sirupsen/logrus
vcs: git vcs: git

View File

@@ -1,6 +1,6 @@
package: github.com/fnproject/fn/cli package: github.com/fnproject/fn/cli
import: import:
- package: github.com/Sirupsen/logrus - package: github.com/sirupsen/logrus
repo: https://github.com/sirupsen/logrus repo: https://github.com/sirupsen/logrus
vcs: git vcs: git
version: v0.11.5 version: v0.11.5

View File

@@ -17,7 +17,7 @@ import (
"time" "time"
"fmt" "fmt"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/coreos/go-semver/semver" "github.com/coreos/go-semver/semver"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"

View File

@@ -7,7 +7,7 @@ import (
"net/http/httputil" "net/http/httputil"
"sync" "sync"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/coreos/go-semver/semver" "github.com/coreos/go-semver/semver"
opentracing "github.com/opentracing/opentracing-go" opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/ext"

View File

@@ -5,7 +5,7 @@ import (
"errors" "errors"
"net/http" "net/http"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
) )
var ( var (

View File

@@ -12,7 +12,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/coreos/go-semver/semver" "github.com/coreos/go-semver/semver"
"github.com/fnproject/fn/fnlb/lb" "github.com/fnproject/fn/fnlb/lb"
) )

186
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: b555054a6f86ac84f6104ad9efabdcd85966c8f6574e485be7337c3ee9f29aa0 hash: 2f996070c68f1ff73bd36879e260fc801034e371cb86671aa5d9f97cdcdf7a05
updated: 2017-08-18T21:27:39.98453555+03:00 updated: 2017-08-24T00:10:34.173574746Z
imports: imports:
- name: github.com/amir/raidman - name: github.com/amir/raidman
version: 1ccc43bfb9c93cb401a4025e49c64ba71e5e668b version: 1ccc43bfb9c93cb401a4025e49c64ba71e5e668b
@@ -15,6 +15,10 @@ imports:
version: fa152c58bc15761d0200cb75fe958b89a9d4888e version: fa152c58bc15761d0200cb75fe958b89a9d4888e
subpackages: subpackages:
- winterm - winterm
- name: github.com/beorn7/perks
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
subpackages:
- quantile
- name: github.com/boltdb/bolt - name: github.com/boltdb/bolt
version: 2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8 version: 2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8
- name: github.com/cactus/go-statsd-client - name: github.com/cactus/go-statsd-client
@@ -25,6 +29,28 @@ imports:
version: 230eff6403e22b43f5fba7b28466dae4718934dd version: 230eff6403e22b43f5fba7b28466dae4718934dd
- name: github.com/cenkalti/backoff - name: github.com/cenkalti/backoff
version: 61153c768f31ee5f130071d08fc82b85208528de version: 61153c768f31ee5f130071d08fc82b85208528de
- name: github.com/cloudflare/cfssl
version: fb16c3479c6ecbd2211751586b206f71387349e6
subpackages:
- api
- auth
- certdb
- config
- crypto/pkcs7
- csr
- errors
- helpers
- helpers/derhelpers
- info
- initca
- log
- ocsp/config
- signer
- signer/local
- name: github.com/coreos/etcd
version: 897cadc88c5f4b01a8df6b760ec4b68ff1ece842
subpackages:
- raft/raftpb
- name: github.com/coreos/go-semver - name: github.com/coreos/go-semver
version: 1817cd4bea52af76542157eeabd74b057d1a199e version: 1817cd4bea52af76542157eeabd74b057d1a199e
subpackages: subpackages:
@@ -46,13 +72,14 @@ imports:
- name: github.com/dgrijalva/jwt-go - name: github.com/dgrijalva/jwt-go
version: a539ee1a749a2b895533f979515ac7e6e0f5b650 version: a539ee1a749a2b895533f979515ac7e6e0f5b650
- name: github.com/docker/cli - name: github.com/docker/cli
version: dd585ad4fb8046aa66eb56babef0c63aa5c5244d version: f5a192bcc4c2794e44eb9dd7d91c2be95c5c6342
subpackages: subpackages:
- cli/config/configfile - cli/config/configfile
- cli/config/credentials
- opts
- name: github.com/docker/distribution - name: github.com/docker/distribution
version: 5ccd03d28ae2b23a3b2863216bcb97e9f650f6d2 version: 5f6282db7d65e6d72ad7c2cc66310724a57be716
subpackages: subpackages:
- context
- digestset - digestset
- manifest - manifest
- manifest/schema1 - manifest/schema1
@@ -66,9 +93,8 @@ imports:
- registry/client/transport - registry/client/transport
- registry/storage/cache - registry/storage/cache
- registry/storage/cache/memory - registry/storage/cache/memory
- uuid
- name: github.com/docker/docker - name: github.com/docker/docker
version: 89658bed64c2a8fe05a978e5b87dbec409d57a0f version: cdf870bd0b5fa678b10ef2708cca7ad776b4913c
subpackages: subpackages:
- api/types - api/types
- api/types/blkiodev - api/types/blkiodev
@@ -79,7 +105,9 @@ imports:
- api/types/registry - api/types/registry
- api/types/strslice - api/types/strslice
- api/types/swarm - api/types/swarm
- api/types/swarm/runtime
- api/types/versions - api/types/versions
- daemon/cluster/convert
- opts - opts
- pkg/archive - pkg/archive
- pkg/fileutils - pkg/fileutils
@@ -89,20 +117,65 @@ imports:
- pkg/jsonlog - pkg/jsonlog
- pkg/jsonmessage - pkg/jsonmessage
- pkg/longpath - pkg/longpath
- pkg/mount
- pkg/namesgenerator
- pkg/pools - pkg/pools
- pkg/promise - pkg/promise
- pkg/stdcopy - pkg/stdcopy
- pkg/stringid
- pkg/system - pkg/system
- pkg/tarsum
- pkg/term - pkg/term
- pkg/term/windows - pkg/term/windows
- registry
- registry/resumable
- name: github.com/docker/docker-credential-helpers
version: d3d99348972b70dc8133f4c2219007c05b4f8225
subpackages:
- client
- credentials
- name: github.com/docker/go-connections - name: github.com/docker/go-connections
version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d
subpackages: subpackages:
- nat - nat
- sockets
- tlsconfig
- name: github.com/docker/go-events
version: 9461782956ad83b30282bf90e31fa6a70c255ba9
- name: github.com/docker/go-units - name: github.com/docker/go-units
version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52 version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
- name: github.com/docker/libkv
version: 93ab0e6c056d325dfbb11e1d58a3b4f5f62e7f3c
subpackages:
- store
- name: github.com/docker/libnetwork
version: b53529dabef6f28ce23485a29270cd7b8a61db1b
subpackages:
- datastore
- discoverapi
- types
- name: github.com/docker/libtrust - name: github.com/docker/libtrust
version: aabc10ec26b754e797f9028f4589c5b7bd90dc20 version: aabc10ec26b754e797f9028f4589c5b7bd90dc20
- name: github.com/docker/swarmkit
version: 0554c9bc9a485025e89b8e5c2c1f0d75961906a2
subpackages:
- api
- api/deepcopy
- api/equality
- api/genericresource
- api/naming
- ca
- connectionbroker
- identity
- ioutils
- log
- manager/raftselector
- manager/state
- manager/state/store
- protobuf/plugin
- remotes
- watch
- watch/queue
- name: github.com/eapache/go-resiliency - name: github.com/eapache/go-resiliency
version: b1fe83b5b03f624450823b751b662259ffc6af70 version: b1fe83b5b03f624450823b751b662259ffc6af70
subpackages: subpackages:
@@ -166,17 +239,32 @@ imports:
- name: github.com/go-sql-driver/mysql - name: github.com/go-sql-driver/mysql
version: 3955978caca48c1658a4bb7a9c6a0f084e326af3 version: 3955978caca48c1658a4bb7a9c6a0f084e326af3
- name: github.com/gogo/protobuf - name: github.com/gogo/protobuf
version: fcdc5011193ff531a548e9b0301828d5a5b97fd8 version: 100ba4e885062801d56799d78530b73b178a78f3
subpackages: subpackages:
- gogoproto
- proto - proto
- protoc-gen-gogo/descriptor
- sortkeys
- types
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: ab9f9a6dab164b7d1246e0e688b0ab7b94d8553e version: 5a0f697c9ed9d68fef0116532c6e05cfeae00e55
subpackages: subpackages:
- proto - proto
- ptypes/any
- name: github.com/golang/snappy - name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9 version: 553a641470496b2327abcac10b36396bd98e45c9
- name: github.com/google/btree - name: github.com/google/btree
version: 316fb6d3f031ae8f4d457c6c5186b9e3ded70435 version: 316fb6d3f031ae8f4d457c6c5186b9e3ded70435
- name: github.com/google/certificate-transparency-go
version: 0dac42a6ed448ba220ee315abfaa6d26fd5fc9bb
repo: https://github.com/google/certificate-transparency-go
subpackages:
- asn1
- client
- jsonclient
- tls
- x509
- x509/pkix
- name: github.com/google/go-querystring - name: github.com/google/go-querystring
version: 53e6ce116135b80d037921a7fdd5138cf32d7a8a version: 53e6ce116135b80d037921a7fdd5138cf32d7a8a
subpackages: subpackages:
@@ -185,6 +273,16 @@ imports:
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/gorilla/mux - name: github.com/gorilla/mux
version: ac112f7d75a0714af1bd86ab17749b31f7809640 version: ac112f7d75a0714af1bd86ab17749b31f7809640
- name: github.com/grpc-ecosystem/go-grpc-prometheus
version: 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
- name: github.com/hashicorp/go-immutable-radix
version: 8aac2701530899b64bdea735a1de8da899815220
- name: github.com/hashicorp/go-memdb
version: 2b2d6c35e14e7557ea1003e707d5e179fa315028
- name: github.com/hashicorp/golang-lru
version: 0a025b7e63adc15a622f29b0b2c4c3848243bbf6
subpackages:
- simplelru
- name: github.com/hashicorp/hcl - name: github.com/hashicorp/hcl
version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca
subpackages: subpackages:
@@ -228,6 +326,10 @@ imports:
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
- name: github.com/mattn/go-sqlite3 - name: github.com/mattn/go-sqlite3
version: 6654e412c3c7eabb310d920cf73a2102dbf8c632 version: 6654e412c3c7eabb310d920cf73a2102dbf8c632
- name: github.com/matttproud/golang_protobuf_extensions
version: c12348ce28de40eed0136aa2b644d0ee0650e56c
subpackages:
- pbutil
- name: github.com/Microsoft/go-winio - name: github.com/Microsoft/go-winio
version: 78439966b38d69bf38227fbf57ac8a6fee70f69a version: 78439966b38d69bf38227fbf57ac8a6fee70f69a
- name: github.com/mitchellh/mapstructure - name: github.com/mitchellh/mapstructure
@@ -236,8 +338,13 @@ imports:
version: cd527374f1e5bff4938207604a14f2e38a9cf512 version: cd527374f1e5bff4938207604a14f2e38a9cf512
- name: github.com/opencontainers/go-digest - name: github.com/opencontainers/go-digest
version: 279bed98673dd5bef374d3b6e4b09e2af76183bf version: 279bed98673dd5bef374d3b6e4b09e2af76183bf
- name: github.com/opencontainers/image-spec
version: 97ae57f204b5956aa313d453ead9094ff9056b32
subpackages:
- specs-go
- specs-go/v1
- name: github.com/opencontainers/runc - name: github.com/opencontainers/runc
version: ea35825a6350511ab93fe24e69c0723d6728616d version: ae2948042b08ad3d6d13cd09f40a50ffff4fc688
subpackages: subpackages:
- libcontainer/system - libcontainer/system
- libcontainer/user - libcontainer/user
@@ -268,6 +375,24 @@ imports:
- xxHash32 - xxHash32
- name: github.com/pkg/errors - name: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90 version: c605e284fe17294bda444b34710735b29d1a9d90
- name: github.com/prometheus/client_golang
version: c5b7fccd204277076155f10851dad72b76a49317
subpackages:
- prometheus
- name: github.com/prometheus/client_model
version: 6f3806018612930941127f2a7c6c453ba2c527d2
subpackages:
- go
- name: github.com/prometheus/common
version: 49fee292b27bfff7f354ee0f64e1bc4850462edf
subpackages:
- expfmt
- internal/bitbucket.org/ww/goautoneg
- model
- name: github.com/prometheus/procfs
version: a1dba9ce8baed984a2495b658c82687f8157b98f
subpackages:
- xfs
- name: github.com/PuerkitoBio/purell - name: github.com/PuerkitoBio/purell
version: 7cf257f0a33260797b0febf39f95fccd86aab2a3 version: 7cf257f0a33260797b0febf39f95fccd86aab2a3
- name: github.com/PuerkitoBio/urlesc - name: github.com/PuerkitoBio/urlesc
@@ -277,11 +402,7 @@ imports:
- name: github.com/Shopify/sarama - name: github.com/Shopify/sarama
version: 3fee590c5568849516741670af9331a193bbe2a3 version: 3fee590c5568849516741670af9331a193bbe2a3
- name: github.com/sirupsen/logrus - name: github.com/sirupsen/logrus
version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f version: 89742aefa4b206dcf400792f3bd35b542998eb3b
- name: github.com/Sirupsen/logrus
version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
repo: https://github.com/sirupsen/logrus.git
vcs: git
subpackages: subpackages:
- hooks/syslog - hooks/syslog
- name: github.com/spf13/afero - name: github.com/spf13/afero
@@ -301,30 +422,59 @@ imports:
subpackages: subpackages:
- codec - codec
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: b176d7def5d71bdd214203491f89843ed217f420 version: eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035
subpackages: subpackages:
- bcrypt - bcrypt
- blowfish - blowfish
- ocsp
- pkcs12
- pkcs12/internal/rc2
- ssh/terminal
- name: golang.org/x/net - name: golang.org/x/net
version: 1c05540f6879653db88113bc4a2b70aec4bd491f version: c8c74377599bd978aee1cf3b9b63a8634051cec2
subpackages: subpackages:
- context - context
- context/ctxhttp - context/ctxhttp
- http2
- http2/hpack
- idna - idna
- internal/timeseries
- lex/httplex
- proxy - proxy
- trace
- name: golang.org/x/sys - name: golang.org/x/sys
version: 9f7170bcd8e9f4d3691c06401119c46a769a1e03 version: 9f7170bcd8e9f4d3691c06401119c46a769a1e03
subpackages: subpackages:
- unix - unix
- windows - windows
- name: golang.org/x/text - name: golang.org/x/text
version: e56139fd9c5bc7244c76116c68e500765bb6db6b version: b19bf474d317b857955b12035d2c5acb57ce8b01
subpackages: subpackages:
- secure/bidirule - secure/bidirule
- transform - transform
- unicode/bidi - unicode/bidi
- unicode/norm - unicode/norm
- width - width
- name: google.golang.org/genproto
version: 09f6ed296fc66555a25fe4ce95173148778dfa85
subpackages:
- googleapis/rpc/status
- name: google.golang.org/grpc
version: b8669c35455183da6d5c474ea6e72fbf55183274
subpackages:
- codes
- credentials
- grpclb/grpc_lb_v1
- grpclog
- internal
- keepalive
- metadata
- naming
- peer
- stats
- status
- tap
- transport
- name: gopkg.in/go-playground/validator.v8 - name: gopkg.in/go-playground/validator.v8
version: 5f1438d3fca68893a817e4a66806cea46a9e4ebf version: 5f1438d3fca68893a817e4a66806cea46a9e4ebf
- name: gopkg.in/mgo.v2 - name: gopkg.in/mgo.v2

View File

@@ -2,18 +2,14 @@ package: github.com/fnproject/fn
excludeDirs: excludeDirs:
- cli - cli
import: import:
- package: golang.org/x/crypto/pkcs12
version: master
- package: github.com/funcy/functions_go - package: github.com/funcy/functions_go
version: ^0.1.35 version: ^0.1.35
subpackages: subpackages:
- models - models
- package: github.com/sirupsen/logrus - package: github.com/sirupsen/logrus
version: ^v0.11.5 version: 89742aefa4b206dcf400792f3bd35b542998eb3b
- package: github.com/Sirupsen/logrus
repo: https://github.com/sirupsen/logrus.git
vcs: git
version: ^v0.11.5
subpackages:
- hooks/syslog
- package: github.com/amir/raidman - package: github.com/amir/raidman
- package: github.com/boltdb/bolt - package: github.com/boltdb/bolt
- package: github.com/cactus/go-statsd-client - package: github.com/cactus/go-statsd-client
@@ -27,11 +23,11 @@ import:
- package: github.com/dghubble/oauth1 - package: github.com/dghubble/oauth1
- package: github.com/dgrijalva/jwt-go - package: github.com/dgrijalva/jwt-go
- package: github.com/docker/cli - package: github.com/docker/cli
version: dd585ad4fb8046aa66eb56babef0c63aa5c5244d version: f5a192bcc4c2794e44eb9dd7d91c2be95c5c6342
subpackages: subpackages:
- cli/config/configfile - cli/config/configfile
- package: github.com/docker/distribution - package: github.com/docker/distribution
version: 5ccd03d28ae2b23a3b2863216bcb97e9f650f6d2 version: 5f6282db7d65e6d72ad7c2cc66310724a57be716
- package: github.com/fsouza/go-dockerclient - package: github.com/fsouza/go-dockerclient
- package: github.com/garyburd/redigo - package: github.com/garyburd/redigo
subpackages: subpackages:
@@ -57,12 +53,9 @@ import:
- package: github.com/jmoiron/jsonq - package: github.com/jmoiron/jsonq
- package: github.com/lib/pq - package: github.com/lib/pq
- package: github.com/docker/docker - package: github.com/docker/docker
version: v17.05.0-ce version: cdf870bd0b5fa678b10ef2708cca7ad776b4913c
- package: github.com/pkg/errors - package: github.com/pkg/errors
- package: github.com/spf13/viper - package: github.com/spf13/viper
- package: golang.org/x/crypto
subpackages:
- bcrypt
- package: gopkg.in/mgo.v2 - package: gopkg.in/mgo.v2
subpackages: subpackages:
- bson - bson
@@ -73,7 +66,7 @@ import:
- package: github.com/opencontainers/go-digest - package: github.com/opencontainers/go-digest
version: 279bed98673dd5bef374d3b6e4b09e2af76183bf version: 279bed98673dd5bef374d3b6e4b09e2af76183bf
- package: github.com/opencontainers/runc - package: github.com/opencontainers/runc
version: ea35825a6350511ab93fe24e69c0723d6728616d version: ae2948042b08ad3d6d13cd09f40a50ffff4fc688
- package: github.com/Azure/go-ansiterm - package: github.com/Azure/go-ansiterm
version: fa152c58bc15761d0200cb75fe958b89a9d4888e version: fa152c58bc15761d0200cb75fe958b89a9d4888e
testImport: testImport:

View File

@@ -1,59 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
// "os"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log.Level = logrus.DebugLevel
}
func main() {
defer func() {
err := recover()
if err != nil {
log.WithFields(logrus.Fields{
"omg": true,
"err": err,
"number": 100,
}).Fatal("The ice breaks!")
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"number": 8,
}).Debug("Started observing beach")
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"temperature": -4,
}).Debug("Temperature changes")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
}

View File

@@ -1,30 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
}
func main() {
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}

View File

@@ -1,10 +0,0 @@
// +build appengine
package logrus
import "io"
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
return true
}

View File

@@ -1,28 +0,0 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux darwin freebsd openbsd netbsd dragonfly
// +build !appengine
package logrus
import (
"io"
"os"
"syscall"
"unsafe"
)
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
var termios Termios
switch v := f.(type) {
case *os.File:
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
default:
return false
}
}

View File

@@ -1,21 +0,0 @@
// +build solaris,!appengine
package logrus
import (
"io"
"os"
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
switch v := f.(type) {
case *os.File:
_, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
return err == nil
default:
return false
}
}

View File

@@ -1,33 +0,0 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows,!appengine
package logrus
import (
"io"
"os"
"syscall"
"unsafe"
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
)
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
switch v := f.(type) {
case *os.File:
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
default:
return false
}
}

2
vendor/github.com/beorn7/perks/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
*.test
*.prof

20
vendor/github.com/beorn7/perks/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright (C) 2013 Blake Mizerany
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

31
vendor/github.com/beorn7/perks/README.md generated vendored Normal file
View File

@@ -0,0 +1,31 @@
# Perks for Go (golang.org)
Perks contains the Go package quantile that computes approximate quantiles over
an unbounded data stream within low memory and CPU bounds.
For more information and examples, see:
http://godoc.org/github.com/bmizerany/perks
A very special thank you and shout out to Graham Cormode (Rutgers University),
Flip Korn (AT&T LabsResearch), S. Muthukrishnan (Rutgers University), and
Divesh Srivastava (AT&T LabsResearch) for their research and publication of
[Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf)
Thank you, also:
* Armon Dadgar (@armon)
* Andrew Gerrand (@nf)
* Brad Fitzpatrick (@bradfitz)
* Keith Rarick (@kr)
FAQ:
Q: Why not move the quantile package into the project root?
A: I want to add more packages to perks later.
Copyright (C) 2013 Blake Mizerany
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

26
vendor/github.com/beorn7/perks/histogram/bench_test.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package histogram
import (
"math/rand"
"testing"
)
func BenchmarkInsert10Bins(b *testing.B) {
b.StopTimer()
h := New(10)
b.StartTimer()
for i := 0; i < b.N; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
}
func BenchmarkInsert100Bins(b *testing.B) {
b.StopTimer()
h := New(100)
b.StartTimer()
for i := 0; i < b.N; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
}

108
vendor/github.com/beorn7/perks/histogram/histogram.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
// Package histogram provides a Go implementation of BigML's histogram package
// for Clojure/Java. It is currently experimental.
package histogram
import (
"container/heap"
"math"
"sort"
)
type Bin struct {
Count int
Sum float64
}
func (b *Bin) Update(x *Bin) {
b.Count += x.Count
b.Sum += x.Sum
}
func (b *Bin) Mean() float64 {
return b.Sum / float64(b.Count)
}
type Bins []*Bin
func (bs Bins) Len() int { return len(bs) }
func (bs Bins) Less(i, j int) bool { return bs[i].Mean() < bs[j].Mean() }
func (bs Bins) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
func (bs *Bins) Push(x interface{}) {
*bs = append(*bs, x.(*Bin))
}
func (bs *Bins) Pop() interface{} {
return bs.remove(len(*bs) - 1)
}
func (bs *Bins) remove(n int) *Bin {
if n < 0 || len(*bs) < n {
return nil
}
x := (*bs)[n]
*bs = append((*bs)[:n], (*bs)[n+1:]...)
return x
}
type Histogram struct {
res *reservoir
}
func New(maxBins int) *Histogram {
return &Histogram{res: newReservoir(maxBins)}
}
func (h *Histogram) Insert(f float64) {
h.res.insert(&Bin{1, f})
h.res.compress()
}
func (h *Histogram) Bins() Bins {
return h.res.bins
}
type reservoir struct {
n int
maxBins int
bins Bins
}
func newReservoir(maxBins int) *reservoir {
return &reservoir{maxBins: maxBins}
}
func (r *reservoir) insert(bin *Bin) {
r.n += bin.Count
i := sort.Search(len(r.bins), func(i int) bool {
return r.bins[i].Mean() >= bin.Mean()
})
if i < 0 || i == r.bins.Len() {
// TODO(blake): Maybe use an .insert(i, bin) instead of
// performing the extra work of a heap.Push.
heap.Push(&r.bins, bin)
return
}
r.bins[i].Update(bin)
}
func (r *reservoir) compress() {
for r.bins.Len() > r.maxBins {
minGapIndex := -1
minGap := math.MaxFloat64
for i := 0; i < r.bins.Len()-1; i++ {
gap := gapWeight(r.bins[i], r.bins[i+1])
if minGap > gap {
minGap = gap
minGapIndex = i
}
}
prev := r.bins[minGapIndex]
next := r.bins.remove(minGapIndex + 1)
prev.Update(next)
}
}
func gapWeight(prev, next *Bin) float64 {
return next.Mean() - prev.Mean()
}

View File

@@ -0,0 +1,38 @@
package histogram
import (
"math/rand"
"testing"
)
func TestHistogram(t *testing.T) {
const numPoints = 1e6
const maxBins = 3
h := New(maxBins)
for i := 0; i < numPoints; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
bins := h.Bins()
if g := len(bins); g > maxBins {
t.Fatalf("got %d bins, wanted <= %d", g, maxBins)
}
for _, b := range bins {
t.Logf("%+v", b)
}
if g := count(h.Bins()); g != numPoints {
t.Fatalf("binned %d points, wanted %d", g, numPoints)
}
}
func count(bins Bins) int {
binCounts := 0
for _, b := range bins {
binCounts += b.Count
}
return binCounts
}

63
vendor/github.com/beorn7/perks/quantile/bench_test.go generated vendored Normal file
View File

@@ -0,0 +1,63 @@
package quantile
import (
"testing"
)
func BenchmarkInsertTargeted(b *testing.B) {
b.ReportAllocs()
s := NewTargeted(Targets)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiased(b *testing.B) {
s := NewLowBiased(0.01)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) {
s := NewLowBiased(0.0001)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkQuery(b *testing.B) {
s := NewTargeted(Targets)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}
func BenchmarkQuerySmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}

121
vendor/github.com/beorn7/perks/quantile/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
// +build go1.1
package quantile_test
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/beorn7/perks/quantile"
)
func Example_simple() {
ch := make(chan float64)
go sendFloats(ch)
// Compute the 50th, 90th, and 99th percentile.
q := quantile.NewTargeted(map[float64]float64{
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
})
for v := range ch {
q.Insert(v)
}
fmt.Println("perc50:", q.Query(0.50))
fmt.Println("perc90:", q.Query(0.90))
fmt.Println("perc99:", q.Query(0.99))
fmt.Println("count:", q.Count())
// Output:
// perc50: 5
// perc90: 16
// perc99: 223
// count: 2388
}
func Example_mergeMultipleStreams() {
// Scenario:
// We have multiple database shards. On each shard, there is a process
// collecting query response times from the database logs and inserting
// them into a Stream (created via NewTargeted(0.90)), much like the
// Simple example. These processes expose a network interface for us to
// ask them to serialize and send us the results of their
// Stream.Samples so we may Merge and Query them.
//
// NOTES:
// * These sample sets are small, allowing us to get them
// across the network much faster than sending the entire list of data
// points.
//
// * For this to work correctly, we must supply the same quantiles
// a priori the process collecting the samples supplied to NewTargeted,
// even if we do not plan to query them all here.
ch := make(chan quantile.Samples)
getDBQuerySamples(ch)
q := quantile.NewTargeted(map[float64]float64{0.90: 0.001})
for samples := range ch {
q.Merge(samples)
}
fmt.Println("perc90:", q.Query(0.90))
}
func Example_window() {
// Scenario: We want the 90th, 95th, and 99th percentiles for each
// minute.
ch := make(chan float64)
go sendStreamValues(ch)
tick := time.NewTicker(1 * time.Minute)
q := quantile.NewTargeted(map[float64]float64{
0.90: 0.001,
0.95: 0.0005,
0.99: 0.0001,
})
for {
select {
case t := <-tick.C:
flushToDB(t, q.Samples())
q.Reset()
case v := <-ch:
q.Insert(v)
}
}
}
func sendStreamValues(ch chan float64) {
// Use your imagination
}
func flushToDB(t time.Time, samples quantile.Samples) {
// Use your imagination
}
// This is a stub for the above example. In reality this would hit the remote
// servers via http or something like it.
func getDBQuerySamples(ch chan quantile.Samples) {}
func sendFloats(ch chan<- float64) {
f, err := os.Open("exampledata.txt")
if err != nil {
log.Fatal(err)
}
sc := bufio.NewScanner(f)
for sc.Scan() {
b := sc.Bytes()
v, err := strconv.ParseFloat(string(b), 64)
if err != nil {
log.Fatal(err)
}
ch <- v
}
if sc.Err() != nil {
log.Fatal(sc.Err())
}
close(ch)
}

2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt generated vendored Normal file

File diff suppressed because it is too large Load Diff

292
vendor/github.com/beorn7/perks/quantile/stream.go generated vendored Normal file
View File

@@ -0,0 +1,292 @@
// Package quantile computes approximate quantiles over an unbounded data
// stream within low memory and CPU bounds.
//
// A small amount of accuracy is traded to achieve the above properties.
//
// Multiple streams can be merged before calling Query to generate a single set
// of results. This is meaningful when the streams represent the same type of
// data. See Merge and Samples.
//
// For more detailed information about the algorithm used, see:
//
// Effective Computation of Biased Quantiles over Data Streams
//
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
package quantile
import (
"math"
"sort"
)
// Sample holds an observed value and meta information for compression. JSON
// tags have been added for convenience.
type Sample struct {
Value float64 `json:",string"`
Width float64 `json:",string"`
Delta float64 `json:",string"`
}
// Samples represents a slice of samples. It implements sort.Interface.
type Samples []Sample
func (a Samples) Len() int { return len(a) }
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type invariant func(s *stream, r float64) float64
// NewLowBiased returns an initialized Stream for low-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the lower ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewLowBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * r
}
return newStream(ƒ)
}
// NewHighBiased returns an initialized Stream for high-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the higher ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewHighBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * (s.n - r)
}
return newStream(ƒ)
}
// NewTargeted returns an initialized Stream concerned with a particular set of
// quantile values that are supplied a priori. Knowing these a priori reduces
// space and computation time. The targets map maps the desired quantiles to
// their absolute errors, i.e. the true quantile of a value returned by a query
// is guaranteed to be within (Quantile±Epsilon).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
func NewTargeted(targets map[float64]float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
var m = math.MaxFloat64
var f float64
for quantile, epsilon := range targets {
if quantile*s.n <= r {
f = (2 * epsilon * r) / quantile
} else {
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
}
if f < m {
m = f
}
}
return m
}
return newStream(ƒ)
}
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
// design. Take care when using across multiple goroutines.
type Stream struct {
*stream
b Samples
sorted bool
}
func newStream(ƒ invariant) *Stream {
x := &stream{ƒ: ƒ}
return &Stream{x, make(Samples, 0, 500), true}
}
// Insert inserts v into the stream.
func (s *Stream) Insert(v float64) {
s.insert(Sample{Value: v, Width: 1})
}
func (s *Stream) insert(sample Sample) {
s.b = append(s.b, sample)
s.sorted = false
if len(s.b) == cap(s.b) {
s.flush()
}
}
// Query returns the computed qth percentiles value. If s was created with
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
// will return an unspecified result.
func (s *Stream) Query(q float64) float64 {
if !s.flushed() {
// Fast path when there hasn't been enough data for a flush;
// this also yields better accuracy for small sets of data.
l := len(s.b)
if l == 0 {
return 0
}
i := int(math.Ceil(float64(l) * q))
if i > 0 {
i -= 1
}
s.maybeSort()
return s.b[i].Value
}
s.flush()
return s.stream.query(q)
}
// Merge merges samples into the underlying streams samples. This is handy when
// merging multiple streams from separate threads, database shards, etc.
//
// ATTENTION: This method is broken and does not yield correct results. The
// underlying algorithm is not capable of merging streams correctly.
func (s *Stream) Merge(samples Samples) {
sort.Sort(samples)
s.stream.merge(samples)
}
// Reset reinitializes and clears the list reusing the samples buffer memory.
func (s *Stream) Reset() {
s.stream.reset()
s.b = s.b[:0]
}
// Samples returns stream samples held by s.
func (s *Stream) Samples() Samples {
if !s.flushed() {
return s.b
}
s.flush()
return s.stream.samples()
}
// Count returns the total number of samples observed in the stream
// since initialization.
func (s *Stream) Count() int {
return len(s.b) + s.stream.count()
}
func (s *Stream) flush() {
s.maybeSort()
s.stream.merge(s.b)
s.b = s.b[:0]
}
func (s *Stream) maybeSort() {
if !s.sorted {
s.sorted = true
sort.Sort(s.b)
}
}
func (s *Stream) flushed() bool {
return len(s.stream.l) > 0
}
type stream struct {
n float64
l []Sample
ƒ invariant
}
func (s *stream) reset() {
s.l = s.l[:0]
s.n = 0
}
func (s *stream) insert(v float64) {
s.merge(Samples{{v, 1, 0}})
}
func (s *stream) merge(samples Samples) {
// TODO(beorn7): This tries to merge not only individual samples, but
// whole summaries. The paper doesn't mention merging summaries at
// all. Unittests show that the merging is inaccurate. Find out how to
// do merges properly.
var r float64
i := 0
for _, sample := range samples {
for ; i < len(s.l); i++ {
c := s.l[i]
if c.Value > sample.Value {
// Insert at position i.
s.l = append(s.l, Sample{})
copy(s.l[i+1:], s.l[i:])
s.l[i] = Sample{
sample.Value,
sample.Width,
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
// TODO(beorn7): How to calculate delta correctly?
}
i++
goto inserted
}
r += c.Width
}
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
i++
inserted:
s.n += sample.Width
r += sample.Width
}
s.compress()
}
func (s *stream) count() int {
return int(s.n)
}
func (s *stream) query(q float64) float64 {
t := math.Ceil(q * s.n)
t += math.Ceil(s.ƒ(s, t) / 2)
p := s.l[0]
var r float64
for _, c := range s.l[1:] {
r += p.Width
if r+c.Width+c.Delta > t {
return p.Value
}
p = c
}
return p.Value
}
func (s *stream) compress() {
if len(s.l) < 2 {
return
}
x := s.l[len(s.l)-1]
xi := len(s.l) - 1
r := s.n - 1 - x.Width
for i := len(s.l) - 2; i >= 0; i-- {
c := s.l[i]
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
x.Width += c.Width
s.l[xi] = x
// Remove element at i.
copy(s.l[i:], s.l[i+1:])
s.l = s.l[:len(s.l)-1]
xi -= 1
} else {
x = c
xi = i
}
r -= c.Width
}
}
func (s *stream) samples() Samples {
samples := make(Samples, len(s.l))
copy(samples, s.l)
return samples
}

215
vendor/github.com/beorn7/perks/quantile/stream_test.go generated vendored Normal file
View File

@@ -0,0 +1,215 @@
package quantile
import (
"math"
"math/rand"
"sort"
"testing"
)
var (
Targets = map[float64]float64{
0.01: 0.001,
0.10: 0.01,
0.50: 0.05,
0.90: 0.01,
0.99: 0.001,
}
TargetsSmallEpsilon = map[float64]float64{
0.01: 0.0001,
0.10: 0.001,
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
}
LowQuantiles = []float64{0.01, 0.1, 0.5}
HighQuantiles = []float64{0.99, 0.9, 0.5}
)
const RelativeEpsilon = 0.01
func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for quantile, epsilon := range Targets {
n := float64(len(a))
k := int(quantile * n)
if k < 1 {
k = 1
}
lower := int((quantile - epsilon) * n)
if lower < 1 {
lower = 1
}
upper := int(math.Ceil((quantile + epsilon) * n))
if upper > len(a) {
upper = len(a)
}
w, min, max := a[k-1], a[lower-1], a[upper-1]
if g := s.Query(quantile); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g)
}
}
}
func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range LowQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - RelativeEpsilon) * qu * n)
upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range HighQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n)
upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func populateStream(s *Stream) []float64 {
a := make([]float64, 0, 1e5+100)
for i := 0; i < cap(a); i++ {
v := rand.NormFloat64()
// Add 5% asymmetric outliers.
if i%20 == 0 {
v = v*v + 1
}
s.Insert(v)
a = append(a, v)
}
return a
}
func TestTargetedQuery(t *testing.T) {
rand.Seed(42)
s := NewTargeted(Targets)
a := populateStream(s)
verifyPercsWithAbsoluteEpsilon(t, a, s)
}
func TestTargetedQuerySmallSampleSize(t *testing.T) {
rand.Seed(42)
s := NewTargeted(TargetsSmallEpsilon)
a := []float64{1, 2, 3, 4, 5}
for _, v := range a {
s.Insert(v)
}
verifyPercsWithAbsoluteEpsilon(t, a, s)
// If not yet flushed, results should be precise:
if !s.flushed() {
for φ, want := range map[float64]float64{
0.01: 1,
0.10: 1,
0.50: 3,
0.90: 5,
0.99: 5,
} {
if got := s.Query(φ); got != want {
t.Errorf("want %f for φ=%f, got %f", want, φ, got)
}
}
}
}
func TestLowBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewLowBiased(RelativeEpsilon)
a := populateStream(s)
verifyLowPercsWithRelativeEpsilon(t, a, s)
}
func TestHighBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewHighBiased(RelativeEpsilon)
a := populateStream(s)
verifyHighPercsWithRelativeEpsilon(t, a, s)
}
// BrokenTestTargetedMerge is broken, see Merge doc comment.
func BrokenTestTargetedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewTargeted(Targets)
s2 := NewTargeted(Targets)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyPercsWithAbsoluteEpsilon(t, a, s1)
}
// BrokenTestLowBiasedMerge is broken, see Merge doc comment.
func BrokenTestLowBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewLowBiased(RelativeEpsilon)
s2 := NewLowBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyLowPercsWithRelativeEpsilon(t, a, s2)
}
// BrokenTestHighBiasedMerge is broken, see Merge doc comment.
func BrokenTestHighBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewHighBiased(RelativeEpsilon)
s2 := NewHighBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyHighPercsWithRelativeEpsilon(t, a, s2)
}
func TestUncompressed(t *testing.T) {
q := NewTargeted(Targets)
for i := 100; i > 0; i-- {
q.Insert(float64(i))
}
if g := q.Count(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
// Before compression, Query should have 100% accuracy.
for quantile := range Targets {
w := quantile * 100
if g := q.Query(quantile); g != w {
t.Errorf("want %f, got %f", w, g)
}
}
}
func TestUncompressedSamples(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.001})
for i := 1; i <= 100; i++ {
q.Insert(float64(i))
}
if g := q.Samples().Len(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
}
func TestUncompressedOne(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.01})
q.Insert(3.14)
if g := q.Query(0.90); g != 3.14 {
t.Error("want PI, got", g)
}
}
func TestDefaults(t *testing.T) {
if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 {
t.Errorf("want 0, got %f", g)
}
}

90
vendor/github.com/beorn7/perks/topk/topk.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
package topk
import (
"sort"
)
// http://www.cs.ucsb.edu/research/tech_reports/reports/2005-23.pdf
type Element struct {
Value string
Count int
}
type Samples []*Element
func (sm Samples) Len() int {
return len(sm)
}
func (sm Samples) Less(i, j int) bool {
return sm[i].Count < sm[j].Count
}
func (sm Samples) Swap(i, j int) {
sm[i], sm[j] = sm[j], sm[i]
}
type Stream struct {
k int
mon map[string]*Element
// the minimum Element
min *Element
}
func New(k int) *Stream {
s := new(Stream)
s.k = k
s.mon = make(map[string]*Element)
s.min = &Element{}
// Track k+1 so that less frequenet items contended for that spot,
// resulting in k being more accurate.
return s
}
func (s *Stream) Insert(x string) {
s.insert(&Element{x, 1})
}
func (s *Stream) Merge(sm Samples) {
for _, e := range sm {
s.insert(e)
}
}
func (s *Stream) insert(in *Element) {
e := s.mon[in.Value]
if e != nil {
e.Count++
} else {
if len(s.mon) < s.k+1 {
e = &Element{in.Value, in.Count}
s.mon[in.Value] = e
} else {
e = s.min
delete(s.mon, e.Value)
e.Value = in.Value
e.Count += in.Count
s.min = e
}
}
if e.Count < s.min.Count {
s.min = e
}
}
func (s *Stream) Query() Samples {
var sm Samples
for _, e := range s.mon {
sm = append(sm, e)
}
sort.Sort(sort.Reverse(sm))
if len(sm) < s.k {
return sm
}
return sm[:s.k]
}

57
vendor/github.com/beorn7/perks/topk/topk_test.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package topk
import (
"fmt"
"math/rand"
"sort"
"testing"
)
func TestTopK(t *testing.T) {
stream := New(10)
ss := []*Stream{New(10), New(10), New(10)}
m := make(map[string]int)
for _, s := range ss {
for i := 0; i < 1e6; i++ {
v := fmt.Sprintf("%x", int8(rand.ExpFloat64()))
s.Insert(v)
m[v]++
}
stream.Merge(s.Query())
}
var sm Samples
for x, s := range m {
sm = append(sm, &Element{x, s})
}
sort.Sort(sort.Reverse(sm))
g := stream.Query()
if len(g) != 10 {
t.Fatalf("got %d, want 10", len(g))
}
for i, e := range g {
if sm[i].Value != e.Value {
t.Errorf("at %d: want %q, got %q", i, sm[i].Value, e.Value)
}
}
}
func TestQuery(t *testing.T) {
queryTests := []struct {
value string
expected int
}{
{"a", 1},
{"b", 2},
{"c", 2},
}
stream := New(2)
for _, tt := range queryTests {
stream.Insert(tt.value)
if n := len(stream.Query()); n != tt.expected {
t.Errorf("want %d, got %d", tt.expected, n)
}
}
}

4
vendor/github.com/cloudflare/cfssl/.dockerignore generated vendored Normal file
View File

@@ -0,0 +1,4 @@
cfssl_*
*-amd64
*-386
dist/*

4
vendor/github.com/cloudflare/cfssl/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,4 @@
dist/*
cli/serve/static.rice-box.go
.coverprofile
gopath

77
vendor/github.com/cloudflare/cfssl/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,77 @@
sudo: false
language: go
go:
- 1.7.x
- 1.8.x
# Install g++-4.8 to support std=c++11 for github.com/google/certificate-transparency/go/merkletree
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
install:
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8"; fi
# Used by the certdb tests
services:
- mysql
- postgresql
before_install:
# CFSSL consists of multiple Go packages, which refer to each other by
# their absolute GitHub path, e.g. github.com/cloudflare/crypto/pkcs11key.
# That means, by default, if someone forks the repo and makes changes across
# multiple packages within CFSSL, Travis won't pass for the branch on their
# own repo. To fix that, we move the directory
- mkdir -p $TRAVIS_BUILD_DIR $GOPATH/src/github.com/cloudflare
- test ! -d $GOPATH/src/github.com/cloudflare/cfssl && mv $TRAVIS_BUILD_DIR $GOPATH/src/github.com/cloudflare/cfssl || true
# Only build pull requests, pushes to the master branch, and branches
# starting with `test-`. This is a convenient way to push branches to
# your own fork of the repostiory to ensure Travis passes before submitting
# a PR. For instance, you might run:
# git push myremote branchname:test-branchname
branches:
only:
- master
- /^test-.*$/
before_script:
- go get golang.org/x/tools/cmd/goimports
- go get github.com/onsi/gomega
- go get github.com/onsi/ginkgo
- go get -u github.com/golang/lint/golint
- go get github.com/modocache/gover
- go get -v github.com/GeertJohan/fgt
- go get -u honnef.co/go/tools/cmd/staticcheck
# Setup DBs + run migrations
- go get bitbucket.org/liamstask/goose/cmd/goose
- if [[ $(uname -s) == 'Linux' ]]; then
psql -c 'create database certdb_development;' -U postgres;
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/pg up;
mysql -e 'create database certdb_development;' -u root;
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/mysql up;
fi
script:
- ./test.sh
notifications:
email:
recipients:
- nick@cloudflare.com
- zi@cloudflare.com
- kyle@cloudflare.com
on_success: never
on_failure: change
env:
global:
- secure: "OmaaZ3jhU9VQ/0SYpenUJEfnmKy/MwExkefFRpDbkRSu/hTQpxxALAZV5WEHo7gxLRMRI0pytLo7w+lAd2FlX1CNcyY62MUicta/8P2twsxp+lR3v1bJ7dwk6qsDbO7Nvv3BKPCDQCHUkggbAEJaHEQGdLk4ursNEB1aGimuCEc="
- GO15VENDOREXPERIMENT=1
matrix:
- BUILD_TAGS="postgresql mysql"
matrix:
include:
- os: osx
go: 1.8.1
env: BUILD_TAGS=
after_success:
- bash <(curl -s https://codecov.io/bash) -f coverprofile.txt

38
vendor/github.com/cloudflare/cfssl/BUILDING.md generated vendored Normal file
View File

@@ -0,0 +1,38 @@
# How to Build CFSSL
## Docker
The requirements to build `CFSSL` are:
1. A running instance of Docker
2. The `bash` shell
To build, run:
$ script/build-docker
This is will build by default all the cfssl command line utilities
for darwin (OSX), linux, and windows for i386 and amd64 and output the
binaries in the current path.
To build a specific platform and OS, run:
$ script/build-docker -os="darwin" -arch="amd64"
Note: for cross-compilation compatibility, the Docker build process will
build programs without PKCS #11.
## Without Docker
The requirements to build without Docker are:
1. Go version 1.5 is the minimum required version of Go. However, only Go 1.6+
is supported due to the test system not supporting Go 1.5.
2. A properly configured go environment
3. A properly configured GOPATH
4. With Go 1.5, you are required to set the environment variable
`GO15VENDOREXPERIMENT=1`.
Run:
$ go install github.com/cloudflare/cfssl/cmd/...

72
vendor/github.com/cloudflare/cfssl/CHANGELOG generated vendored Normal file
View File

@@ -0,0 +1,72 @@
1.1.0 - 2015-08-04
ADDED:
- Revocation now checks OCSP status.
- Authenticated endpoints are now supported using HMAC tags.
- Bundle can verify certificates against a domain or IP.
- OCSP subcommand has been added.
- PKCS #11 keys are now supported; this support is now the default.
- OCSP serving is now implemented.
- The multirootca tool is now available for multiple signing
keys via an authenticated API.
- A scan utility for checking the quality of a server's TLS
configuration.
- The certificate bundler now supports PKCS #7 and PKCS #12.
- An info endpoint has been added to retrieve the signers'
certificates.
- Signers can now use a serial sequence number for certificate
serial numbers; the default remains randomised serial numbers.
- CSR whitelisting allows the signer to explicitly distrust
certain fields in a CSR.
- Signing profiles can include certificate policies and their
qualifiers.
- The multirootca can use Red October-secured private keys.
- The multirootca can whitelist CSRs per-signer based on an
IP network whitelist.
- The signer can whitelist SANs and common names via a regular-
expression whitelist.
- Multiple fallback remote signers are now supported in the
cfssl server.
- A Docker build script has been provided to facilitate building
CFSSL for all supported platforms.
- The log package includes a new logging level, fatal, that
immediately exits with error after printing the log message.
CHANGED:
- CLI tool can read from standard input.
- The -f flag has been renamed to -config.
- Signers have been refactored into local and remote signers
under a single universal signer abstraction.
- The CLI subcommands have been refactored into separate
packages.
- Signing can now extract subject information from a CSR.
- Various improvements to the certificate ubiquity scoring,
such as accounting for SHA1 deprecation.
- The bundle CLI tool can set the intermediates directory that
newly found intermediates can be stored in.
- The CLI tools return exit code 1 on failure.
CONTRIBUTORS:
Alice Xia
Dan Rohr
Didier Smith
Dominic Luechinger
Erik Kristensen
Fabian Ruff
George Tankersley
Harald Wagener
Harry Harpham
Jacob H. Haven
Jacob Hoffman-Andrews
Joshua Kroll
Kyle Isom
Nick Sullivan
Peter Eckersley
Richard Barnes
Sophie Huang
Steve Rude
Tara Vancil
Terin Stock
Thomaz Leite
Travis Truman
Zi Lin

16
vendor/github.com/cloudflare/cfssl/Dockerfile generated vendored Normal file
View File

@@ -0,0 +1,16 @@
FROM golang:1.8.1
ENV USER root
WORKDIR /go/src/github.com/cloudflare/cfssl
COPY . .
# restore all deps and build
RUN go get github.com/GeertJohan/go.rice/rice && rice embed-go -i=./cli/serve && \
cp -R /go/src/github.com/cloudflare/cfssl/vendor/github.com/cloudflare/cfssl_trust /etc/cfssl && \
go install ./cmd/...
EXPOSE 8888
ENTRYPOINT ["cfssl"]
CMD ["--help"]

11
vendor/github.com/cloudflare/cfssl/Dockerfile.build generated vendored Normal file
View File

@@ -0,0 +1,11 @@
FROM golang:1.8.1
ENV USER root
WORKDIR /go/src/github.com/cloudflare/cfssl
COPY . .
# restore all deps and build
RUN go get github.com/mitchellh/gox
ENTRYPOINT ["gox"]

39
vendor/github.com/cloudflare/cfssl/Dockerfile.minimal generated vendored Normal file
View File

@@ -0,0 +1,39 @@
FROM alpine:3.5
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go
ENV USER root
COPY . /go/src/github.com/cloudflare/cfssl
RUN buildDeps=' \
go \
git \
gcc \
libc-dev \
libtool \
libgcc \
' \
set -x && \
apk update && \
apk add $buildDeps && \
cd /go/src/github.com/cloudflare/cfssl && \
go get github.com/GeertJohan/go.rice/rice && rice embed-go -i=./cli/serve && \
cp -R /go/src/github.com/cloudflare/cfssl/vendor/github.com/cloudflare/cfssl_trust /etc/cfssl && \
go build -o /usr/bin/cfssl ./cmd/cfssl && \
go build -o /usr/bin/cfssljson ./cmd/cfssljson && \
go build -o /usr/bin/mkbundle ./cmd/mkbundle && \
go build -o /usr/bin/multirootca ./cmd/multirootca && \
apk del $buildDeps && \
rm -rf /var/cache/apk/* && \
rm -rf /go && \
echo "Build complete."
VOLUME [ "/etc/cfssl" ]
WORKDIR /etc/cfssl
EXPOSE 8888
ENTRYPOINT ["cfssl"]
CMD ["--help"]

24
vendor/github.com/cloudflare/cfssl/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2014 CloudFlare Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

447
vendor/github.com/cloudflare/cfssl/README.md generated vendored Normal file
View File

@@ -0,0 +1,447 @@
# CFSSL
[![Build Status](https://travis-ci.org/cloudflare/cfssl.svg?branch=master)](https://travis-ci.org/cloudflare/cfssl)
[![Coverage Status](http://codecov.io/github/cloudflare/cfssl/coverage.svg?branch=master)](http://codecov.io/github/cloudflare/cfssl?branch=master)
[![GoDoc](https://godoc.org/github.com/cloudflare/cfssl?status.svg)](https://godoc.org/github.com/cloudflare/cfssl)
## CloudFlare's PKI/TLS toolkit
CFSSL is CloudFlare's PKI/TLS swiss army knife. It is both a command line
tool and an HTTP API server for signing, verifying, and bundling TLS
certificates. It requires Go 1.6+ to build.
Note that certain linux distributions have certain algorithms removed
(RHEL-based distributions in particular), so the golang from the
official repositories will not work. Users of these distributions should
[install go manually](//golang.org/dl) to install CFSSL.
CFSSL consists of:
* a set of packages useful for building custom TLS PKI tools
* the `cfssl` program, which is the canonical command line utility
using the CFSSL packages.
* the `multirootca` program, which is a certificate authority server
that can use multiple signing keys.
* the `mkbundle` program is used to build certificate pool bundles.
* the `cfssljson` program, which takes the JSON output from the
`cfssl` and `multirootca` programs and writes certificates, keys,
CSRs, and bundles to disk.
### Building
See [BUILDING](BUILDING.md)
### Installation
Installation requires a
[working Go 1.6+ installation](http://golang.org/doc/install) and a
properly set `GOPATH`.
```
$ go get -u github.com/cloudflare/cfssl/cmd/cfssl
```
will download and build the CFSSL tool, installing it in
`$GOPATH/bin/cfssl`. To install the other utility programs that are in
this repo:
```
$ go get -u github.com/cloudflare/cfssl/cmd/...
```
This will download, build, and install `cfssl`, `cfssljson`, and
`mkbundle` into `$GOPATH/bin/`.
#### Installing pre-Go 1.6
With a Go 1.5 installation, CFSSL will still probably build. However,
the test system uses [`golint`](https://github.com/golang/lint), which
no longer works on Go 1.5. As our test suite can't cover Go 1.5 anymore,
we no longer support it.
Note that CFSSL makes use of vendored packages; in Go 1.5, the
`GO15VENDOREXPERIMENT` environment variable will need to be set, e.g.
```
export GO15VENDOREXPERIMENT=1
```
With a Go 1.4 or earlier installation, you won't be able to install the
latest version of CFSSL. However, you can checkout the `1.1.0` release
and build that.
```
git clone -b 1.1.0 https://github.com/cloudflare/cfssl.git $GOPATH/src/github.com/cloudflare/cfssl
go get github.com/cloudflare/cfssl/cmd/cfssl
```
### Using the Command Line Tool
The `cfssl` command line tool takes a command to specify what
operation it should carry out:
sign signs a certificate
bundle build a certificate bundle
genkey generate a private key and a certificate request
gencert generate a private key and a certificate
serve start the API server
version prints out the current version
selfsign generates a self-signed certificate
print-defaults print default configurations
Use "cfssl [command] -help" to find out more about a command.
The version command takes no arguments.
#### Signing
```
cfssl sign [-ca cert] [-ca-key key] [-hostname comma,separated,hostnames] csr [subject]
```
The csr is the client's certificate request. The `-ca` and `-ca-key`
flags are the CA's certificate and private key, respectively. By
default, they are "ca.pem" and "ca_key.pem". The `-hostname` is
a comma separated hostname list that overrides the DNS names and
IP address in the certificate SAN extension.
For example, assuming the CA's private key is in
`/etc/ssl/private/cfssl_key.pem` and the CA's certificate is in
`/etc/ssl/certs/cfssl.pem`, to sign the `cloudflare.pem` certificate
for cloudflare.com:
```
cfssl sign -ca /etc/ssl/certs/cfssl.pem \
-ca-key /etc/ssl/private/cfssl_key.pem \
-hostname cloudflare.com ./cloudflare.pem
```
It is also possible to specify csr through '-csr' flag. By doing so,
flag values take precedence and will overwrite the argument.
The subject is an optional file that contains subject information that
should be used in place of the information from the CSR. It should be
a JSON file with the type:
```json
{
"CN": "example.com",
"names": [
{
"C": "US",
"L": "San Francisco",
"O": "Internet Widgets, Inc.",
"OU": "WWW",
"ST": "California"
}
]
}
```
**N.B.** As of Go 1.7, self-signed certificates will not include
[the AKI](https://go.googlesource.com/go/+/b623b71509b2d24df915d5bc68602e1c6edf38ca).
#### Bundling
```
cfssl bundle [-ca-bundle bundle] [-int-bundle bundle] \
[-metadata metadata_file] [-flavor bundle_flavor] \
-cert certificate_file [-key key_file]
```
The bundles are used for the root and intermediate certificate
pools. In addition, platform metadata is specified through '-metadata'
The bundle files, metadata file (and auxiliary files) can be
found at [cfssl_trust](https://github.com/cloudflare/cfssl_trust)
Specify PEM-encoded client certificate and key through '-cert' and
'-key' respectively. If key is specified, the bundle will be built
and verified with the key. Otherwise the bundle will be built
without a private key. Instead of file path, use '-' for reading
certificate PEM from stdin. It is also acceptable the certificate
file contains a (partial) certificate bundle.
Specify bundling flavor through '-flavor'. There are three flavors:
'optimal' to generate a bundle of shortest chain and most advanced
cryptographic algorithms, 'ubiquitous' to generate a bundle of most
widely acceptance across different browsers and OS platforms, and
'force' to find an acceptable bundle which is identical to the
content of the input certificate file.
Alternatively, the client certificate can be pulled directly from
a domain. It is also possible to connect to the remote address
through '-ip'.
```
cfssl bundle [-ca-bundle bundle] [-int-bundle bundle] \
[-metadata metadata_file] [-flavor bundle_flavor] \
-domain domain_name [-ip ip_address]
```
The bundle output form should follow the example
```json
{
"bundle": "CERT_BUNDLE_IN_PEM",
"crt": "LEAF_CERT_IN_PEM",
"crl_support": true,
"expires": "2015-12-31T23:59:59Z",
"hostnames": ["example.com"],
"issuer": "ISSUER CERT SUBJECT",
"key": "KEY_IN_PEM",
"key_size": 2048,
"key_type": "2048-bit RSA",
"ocsp": ["http://ocsp.example-ca.com"],
"ocsp_support": true,
"root": "ROOT_CA_CERT_IN_PEM",
"signature": "SHA1WithRSA",
"subject": "LEAF CERT SUBJECT",
"status": {
"rebundled": false,
"expiring_SKIs": [],
"untrusted_root_stores": [],
"messages": [],
"code": 0
}
}
```
#### Generating certificate signing request and private key
```
cfssl genkey csr.json
```
To generate a private key and corresponding certificate request, specify
the key request as a JSON file. This file should follow the form
```json
{
"hosts": [
"example.com",
"www.example.com"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "San Francisco",
"O": "Internet Widgets, Inc.",
"OU": "WWW",
"ST": "California"
}
]
}
```
#### Generating self-signed root CA certificate and private key
```
cfssl genkey -initca csr.json | cfssljson -bare ca
```
To generate a self-signed root CA certificate, specify the key request as
the JSON file in the same format as in 'genkey'. Three PEM-encoded entities
will appear in the output: the private key, the csr, and the self-signed
certificate.
#### Generating a remote-issued certificate and private key.
```
cfssl gencert -remote=remote_server [-hostname=comma,separated,hostnames] csr.json
```
This is calls genkey, but has a remote CFSSL server sign and issue
a certificate. You may use `-hostname` to override certificate SANs.
#### Generating a local-issued certificate and private key.
```
cfssl gencert -ca cert -ca-key key [-hostname=comma,separated,hostnames] csr.json
```
This is generates and issues a certificate and private key from a local CA
via a JSON request. You may use `-hostname` to override certificate SANs.
#### Updating a OCSP responses file with a newly issued certificate
```
cfssl ocspsign -ca cert -responder key -responder-key key -cert cert \
| cfssljson -bare -stdout >> responses
```
This will generate a OCSP response for the `cert` and add it to the
`responses` file. You can then pass `responses` to `ocspserve` to start a
OCSP server.
### Starting the API Server
CFSSL comes with an HTTP-based API server; the endpoints are
documented in `doc/api/intro.txt`. The server is started with the "serve"
command:
```
cfssl serve [-address address] [-ca cert] [-ca-bundle bundle] \
[-ca-key key] [-int-bundle bundle] [-int-dir dir] [-port port] \
[-metadata file] [-remote remote_host] [-config config] \
[-responder cert] [-responder-key key] [-db-config db-config]
```
Address and port default to "127.0.0.1:8888". The `-ca` and `-ca-key`
arguments should be the PEM-encoded certificate and private key to use
for signing; by default, they are "ca.pem" and "ca_key.pem". The
`-ca-bundle` and `-int-bundle` should be the certificate bundles used
for the root and intermediate certificate pools, respectively. These
default to "ca-bundle.crt" and "int-bundle." If the "remote" option is
provided, all signature operations will be forwarded to the remote CFSSL.
'-int-dir' specifies intermediates directory. '-metadata' is a file for
root certificate presence. The content of the file is a json dictionary
(k,v): each key k is SHA-1 digest of a root certificate while value v
is a list of key store filenames. '-config' specifies path to configuration
file. '-responder' and '-responder-key' are Certificate for OCSP responder
and private key for OCSP responder certificate, respectively.
The amount of logging can be controlled with the `-loglevel` option. This
comes *after* the serve command:
```
cfssl serve -loglevel 2
```
The levels are:
* 0. DEBUG
* 1. INFO (this is the default level)
* 2. WARNING
* 3. ERROR
* 4. CRITICAL
### The multirootca
The `cfssl` program can act as an online certificate authority, but it
only uses a single key. If multiple signing keys are needed, the
`multirootca` program can be used. It only provides the sign,
authsign, and info endpoints. The documentation contains instructions
for configuring and running the CA.
### The mkbundle Utility
`mkbundle` is used to build the root and intermediate bundles used in
verifying certificates. It can be installed with
```
go get -u github.com/cloudflare/cfssl/cmd/mkbundle
```
It takes a collection of certificates, checks for CRL revocation (OCSP
support is planned for the next release) and expired certificates, and
bundles them into one file. It takes directories of certificates and
certificate files (which may contain multiple certificates). For example,
if the directory `intermediates` contains a number of intermediate
certificates,
```
mkbundle -f int-bundle.crt intermediates
```
will check those certificates and combine valid ones into a single
`int-bundle.crt` file.
The `-f` flag specifies an output name; `-loglevel` specifies the verbosity
of the logging (using the same loglevels above), and `-nw` controls the
number of revocation-checking workers.
### The cfssljson Utility
Most of the output from `cfssl` is in JSON. The `cfssljson` will take
this output and split it out into separate key, certificate, CSR, and
bundle files as appropriate. The tool takes a single flag, `-f`, that
specifies the input file, and an argument that specifies the base name for
the files produced. If the input filename is "-" (which is the default),
`cfssljson` reads from standard input. It maps keys in the JSON file to
filenames in the following way:
* if there is a "cert" (or if not, if there's a "certificate") field, the
file "basename.pem" will be produced.
* if there is a "key" (or if not, if there's a "private_key") field, the
file "basename-key.pem" will be produced.
* if there is a "csr" (or if not, if there's a "certificate_request") field,
the file "basename.csr" will be produced.
* if there is a "bundle" field, the file "basename-bundle.pem" will
be produced.
* if there is a "ocspResponse" field, the file "basename-response.der" will
be produced.
Instead of saving to a file, you can pass `-stdout` to output the encoded
contents.
### Static Builds
By default, the web assets are accessed from disk, based on their
relative locations. If youre wishing to distribute a single,
statically-linked, cfssl binary, youll want to embed these resources
before building. This can by done with the
[go.rice](https://github.com/GeertJohan/go.rice) tool.
```
pushd cli/serve && rice embed-go && popd
```
Then building with `go build` will use the embedded resources.
### Using a PKCS#11 hardware token / HSM
For better security, you may want to store your private key in an HSM or
smartcard. The interface to both of these categories of device is described by
the PKCS#11 spec. If you need to do approximately one signing operation per
second or fewer, the Yubikey NEO and NEO-n are inexpensive smartcard options:
https://www.yubico.com/products/yubikey-hardware/yubikey-neo/. In general you
are looking for a product that supports PIV (personal identity verification). If
your signing needs are in the hundreds of signatures per second, you will need
to purchase an expensive HSM (in the thousands to many thousands of USD).
If you want to try out the PKCS#11 signing modes without a hardware token, you
can use the [SoftHSM](https://github.com/opendnssec/SoftHSMv1#softhsm)
implementation. Please note that using SoftHSM simply stores your private key in
a file on disk and does not increase security.
To get started with your PKCS#11 token you will need to initialize it with a
private key, PIN, and token label. The instructions to do this will be specific
to each hardware device, and you should follow the instructions provided by your
vendor. You will also need to find the path to your 'module', a shared object
file (.so). Having initialized your device, you can query it to check your token
label with:
pkcs11-tool --module <module path> --list-token-slots
You'll also want to check the label of the private key you imported (or
generated). Run the following command and look for a 'Private Key Object':
pkcs11-tool --module <module path> --pin <pin> \
--list-token-slots --login --list-objects
You now have all the information you need to use your PKCS#11 token with CFSSL.
CFSSL supports PKCS#11 for certificate signing and OCSP signing. To create a
Signer (for certificate signing), import `signer/universal` and call NewSigner
with a Root object containing the module, pin, token label and private label
from above, plus a path to your certificate. The structure of the Root object is
documented in universal.go.
Alternately, you can construct a pkcs11key.Key or pkcs11key.Pool yourself, and
pass it to ocsp.NewSigner (for OCSP) or local.NewSigner (for certificate
signing). This will be necessary, for example, if you are using a single-session
token like the Yubikey and need both OCSP signing and certificate signing at the
same time.
### Additional Documentation
Additional documentation can be found in the "doc/" directory:
* `api/intro.txt`: documents the API endpoints
* `bootstrap.txt`: a walkthrough from building the package to getting
up and running

231
vendor/github.com/cloudflare/cfssl/api/api.go generated vendored Normal file
View File

@@ -0,0 +1,231 @@
// Package api implements an HTTP-based API and server for CFSSL.
package api
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
)
// Handler is an interface providing a generic mechanism for handling HTTP requests.
type Handler interface {
Handle(w http.ResponseWriter, r *http.Request) error
}
// HTTPHandler is a wrapper that encapsulates Handler interface as http.Handler.
// HTTPHandler also enforces that the Handler only responds to requests with registered HTTP methods.
type HTTPHandler struct {
Handler // CFSSL handler
Methods []string // The associated HTTP methods
}
// HandlerFunc is similar to the http.HandlerFunc type; it serves as
// an adapter allowing the use of ordinary functions as Handlers. If
// f is a function with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(http.ResponseWriter, *http.Request) error
// Handle calls f(w, r)
func (f HandlerFunc) Handle(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Content-Type", "application/json")
return f(w, r)
}
// HandleError is the centralised error handling and reporting.
func HandleError(w http.ResponseWriter, err error) (code int) {
if err == nil {
return http.StatusOK
}
msg := err.Error()
httpCode := http.StatusInternalServerError
// If it is recognized as HttpError emitted from cfssl,
// we rewrite the status code accordingly. If it is a
// cfssl error, set the http status to StatusBadRequest
switch err := err.(type) {
case *errors.HTTPError:
httpCode = err.StatusCode
code = err.StatusCode
case *errors.Error:
httpCode = http.StatusBadRequest
code = err.ErrorCode
msg = err.Message
}
response := NewErrorResponse(msg, code)
jsonMessage, err := json.Marshal(response)
if err != nil {
log.Errorf("Failed to marshal JSON: %v", err)
} else {
msg = string(jsonMessage)
}
http.Error(w, msg, httpCode)
return code
}
// ServeHTTP encapsulates the call to underlying Handler to handle the request
// and return the response with proper HTTP status code
func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var err error
var match bool
// Throw 405 when requested with an unsupported verb.
for _, m := range h.Methods {
if m == r.Method {
match = true
}
}
if match {
err = h.Handle(w, r)
} else {
err = errors.NewMethodNotAllowed(r.Method)
}
status := HandleError(w, err)
log.Infof("%s - \"%s %s\" %d", r.RemoteAddr, r.Method, r.URL, status)
}
// readRequestBlob takes a JSON-blob-encoded response body in the form
// map[string]string and returns it, the list of keywords presented,
// and any error that occurred.
func readRequestBlob(r *http.Request) (map[string]string, error) {
var blob map[string]string
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
r.Body.Close()
err = json.Unmarshal(body, &blob)
if err != nil {
return nil, err
}
return blob, nil
}
// ProcessRequestOneOf reads a JSON blob for the request and makes
// sure it contains one of a set of keywords. For example, a request
// might have the ('foo' && 'bar') keys, OR it might have the 'baz'
// key. In either case, we want to accept the request; however, if
// none of these sets shows up, the request is a bad request, and it
// should be returned.
func ProcessRequestOneOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
blob, err := readRequestBlob(r)
if err != nil {
return nil, nil, err
}
var matched []string
for _, set := range keywordSets {
if matchKeywords(blob, set) {
if matched != nil {
return nil, nil, errors.NewBadRequestString("mismatched parameters")
}
matched = set
}
}
if matched == nil {
return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
}
return blob, matched, nil
}
// ProcessRequestFirstMatchOf reads a JSON blob for the request and returns
// the first match of a set of keywords. For example, a request
// might have one of the following combinations: (foo=1, bar=2), (foo=1), and (bar=2)
// By giving a specific ordering of those combinations, we could decide how to accept
// the request.
func ProcessRequestFirstMatchOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
blob, err := readRequestBlob(r)
if err != nil {
return nil, nil, err
}
for _, set := range keywordSets {
if matchKeywords(blob, set) {
return blob, set, nil
}
}
return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
}
func matchKeywords(blob map[string]string, keywords []string) bool {
for _, keyword := range keywords {
if _, ok := blob[keyword]; !ok {
return false
}
}
return true
}
// ResponseMessage implements the standard for response errors and
// messages. A message has a code and a string message.
type ResponseMessage struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Response implements the CloudFlare standard for API
// responses.
type Response struct {
Success bool `json:"success"`
Result interface{} `json:"result"`
Errors []ResponseMessage `json:"errors"`
Messages []ResponseMessage `json:"messages"`
}
// NewSuccessResponse is a shortcut for creating new successul API
// responses.
func NewSuccessResponse(result interface{}) Response {
return Response{
Success: true,
Result: result,
Errors: []ResponseMessage{},
Messages: []ResponseMessage{},
}
}
// NewSuccessResponseWithMessage is a shortcut for creating new successul API
// responses that includes a message.
func NewSuccessResponseWithMessage(result interface{}, message string, code int) Response {
return Response{
Success: true,
Result: result,
Errors: []ResponseMessage{},
Messages: []ResponseMessage{{code, message}},
}
}
// NewErrorResponse is a shortcut for creating an error response for a
// single error.
func NewErrorResponse(message string, code int) Response {
return Response{
Success: false,
Result: nil,
Errors: []ResponseMessage{{code, message}},
Messages: []ResponseMessage{},
}
}
// SendResponse builds a response from the result, sets the JSON
// header, and writes to the http.ResponseWriter.
func SendResponse(w http.ResponseWriter, result interface{}) error {
response := NewSuccessResponse(result)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err := enc.Encode(response)
return err
}
// SendResponseWithMessage builds a response from the result and the
// provided message, sets the JSON header, and writes to the
// http.ResponseWriter.
func SendResponseWithMessage(w http.ResponseWriter, result interface{}, message string, code int) error {
response := NewSuccessResponseWithMessage(result, message, code)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err := enc.Encode(response)
return err
}

220
vendor/github.com/cloudflare/cfssl/api/api_test.go generated vendored Normal file
View File

@@ -0,0 +1,220 @@
package api
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
const (
ty = "Thank you!"
deny = "That's not true!"
)
func simpleHandle(w http.ResponseWriter, r *http.Request) error {
_, _, err := ProcessRequestOneOf(r, [][]string{
{"compliment"},
{"critique"},
})
if err != nil {
return err
}
return SendResponse(w, ty)
}
func cleverHandle(w http.ResponseWriter, r *http.Request) error {
_, matched, err := ProcessRequestFirstMatchOf(r, [][]string{
{"compliment"},
{"critique"},
})
if err != nil {
return err
}
if matched[0] == "critique" {
return SendResponse(w, deny)
}
return SendResponse(w, ty)
}
func post(t *testing.T, obj map[string]interface{}, ts *httptest.Server) (resp *http.Response, body []byte) {
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func get(t *testing.T, ts *httptest.Server) (resp *http.Response, body []byte) {
resp, err := http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func TestRigidHandle(t *testing.T) {
ts := httptest.NewServer(HTTPHandler{Handler: HandlerFunc(simpleHandle), Methods: []string{"POST"}})
defer ts.Close()
// Response to compliment
obj := map[string]interface{}{}
obj["compliment"] = "it's good"
resp, body := post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message := new(Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// Response to critique
obj = map[string]interface{}{}
obj["critique"] = "it's bad"
resp, body = post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message = new(Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// reject mixed review
obj = map[string]interface{}{}
obj["critique"] = "it's OK"
obj["compliment"] = "it's not bad"
resp, _ = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject empty review
obj = map[string]interface{}{}
resp, _ = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject GET
resp, _ = get(t, ts)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Test expected 405, have %d", resp.StatusCode)
}
}
func TestCleverHandle(t *testing.T) {
ts := httptest.NewServer(HTTPHandler{Handler: HandlerFunc(cleverHandle), Methods: []string{"POST"}})
defer ts.Close()
// Response ty to compliment
obj := map[string]interface{}{}
obj["compliment"] = "it's good"
resp, body := post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message := new(Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// Response deny to critique
obj = map[string]interface{}{}
obj["critique"] = "it's bad"
resp, body = post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message = new(Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != deny {
t.Fatal("Wrong response")
}
// Be polite to mixed review
obj = map[string]interface{}{}
obj["critique"] = "it's OK"
obj["compliment"] = "it's not bad"
_, body = post(t, obj, ts)
message = new(Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// reject empty review
obj = map[string]interface{}{}
resp, _ = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject GET
resp, _ = get(t, ts)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Test expected 405, have %d", resp.StatusCode)
}
}

View File

@@ -0,0 +1,91 @@
// Package bundle implements the HTTP handler for the bundle command.
package bundle
import (
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/bundler"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
)
// Handler accepts requests for either remote or uploaded
// certificates to be bundled, and returns a certificate bundle (or
// error).
type Handler struct {
bundler *bundler.Bundler
}
// NewHandler creates a new bundler that uses the root bundle and
// intermediate bundle in the trust chain.
func NewHandler(caBundleFile, intBundleFile string) (http.Handler, error) {
var err error
b := new(Handler)
if b.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile); err != nil {
return nil, err
}
log.Info("bundler API ready")
return api.HTTPHandler{Handler: b, Methods: []string{"POST"}}, nil
}
// Handle implements an http.Handler interface for the bundle handler.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
blob, matched, err := api.ProcessRequestFirstMatchOf(r,
[][]string{
{"certificate"},
{"domain"},
})
if err != nil {
log.Warningf("invalid request: %v", err)
return err
}
flavor := blob["flavor"]
bf := bundler.Ubiquitous
if flavor != "" {
bf = bundler.BundleFlavor(flavor)
}
log.Infof("request for flavor %v", bf)
var result *bundler.Bundle
switch matched[0] {
case "domain":
bundle, err := h.bundler.BundleFromRemote(blob["domain"], blob["ip"], bf)
if err != nil {
log.Warningf("couldn't bundle from remote: %v", err)
return err
}
result = bundle
case "certificate":
bundle, err := h.bundler.BundleFromPEMorDER([]byte(blob["certificate"]), []byte(blob["private_key"]), bf, "")
if err != nil {
log.Warning("bad PEM certifcate or private key")
return err
}
serverName := blob["domain"]
ip := blob["ip"]
if serverName != "" {
err := bundle.Cert.VerifyHostname(serverName)
if err != nil {
return errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
}
if ip != "" {
err := bundle.Cert.VerifyHostname(ip)
if err != nil {
return errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
}
result = bundle
}
log.Info("wrote response")
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,213 @@
package bundle
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudflare/cfssl/api"
)
const (
testCaBundleFile = "../testdata/ca-bundle.pem"
testIntBundleFile = "../testdata/int-bundle.pem"
testLeafCertFile = "../testdata/leaf.pem"
testLeafKeyFile = "../testdata/leaf.key"
testLeafWrongKeyFile = "../testdata/leaf.badkey"
testBrokenCertFile = "../testdata/broken.pem"
)
func newTestHandler(t *testing.T) (h http.Handler) {
h, err := NewHandler(testCaBundleFile, testIntBundleFile)
if err != nil {
t.Fatal(err)
}
return
}
func newBundleServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testBundleFile(t *testing.T, domain, ip, certFile, keyFile, flavor string) (resp *http.Response, body []byte) {
ts := newBundleServer(t)
defer ts.Close()
var certPEM, keyPEM []byte
if certFile != "" {
var err error
certPEM, err = ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
}
if keyFile != "" {
var err error
keyPEM, err = ioutil.ReadFile(keyFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]string{"flavor": flavor}
if len(domain) > 0 {
obj["domain"] = domain
}
if len(ip) > 0 {
obj["ip"] = ip
}
if len(certPEM) > 0 {
obj["certificate"] = string(certPEM)
}
if len(keyPEM) > 0 {
obj["private_key"] = string(keyPEM)
}
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
type bundleTest struct {
Domain string
IP string
CertFile string
KeyFile string
Flavor string
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
}
var bundleTests = []bundleTest{
// Test bundling with certificate
{
CertFile: testLeafCertFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
Flavor: "ubiquitous",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
Flavor: "optimal",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
KeyFile: testLeafKeyFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
Domain: "cfssl-leaf.com",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
// Test bundling with remote domain
{
Domain: "google.com",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
},
// Error testing.
{
CertFile: testLeafCertFile,
KeyFile: testLeafWrongKeyFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 2300,
},
{
// no input parameter is specified
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
CertFile: testBrokenCertFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 1003,
},
{
CertFile: testLeafKeyFile,
KeyFile: testLeafKeyFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 1003,
},
{
CertFile: testLeafCertFile,
KeyFile: testLeafCertFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 2003,
},
{
CertFile: testLeafCertFile,
Domain: "cloudflare-leaf.com",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 1200,
},
}
func TestBundle(t *testing.T) {
for i, test := range bundleTests {
resp, body := testBundleFile(t, test.Domain, test.IP, test.CertFile, test.KeyFile, test.Flavor)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Errorf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Errorf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != 0 && test.ExpectedErrorCode != message.Errors[0].Code {
t.Errorf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}

View File

@@ -0,0 +1,193 @@
package certadd
import (
"bytes"
"encoding/hex"
"encoding/json"
"io/ioutil"
"math/big"
"net/http"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/ocsp"
"encoding/base64"
stdocsp "golang.org/x/crypto/ocsp"
)
// This is patterned on
// https://github.com/cloudflare/cfssl/blob/master/api/revoke/revoke.go. This
// file defines an HTTP endpoint handler that accepts certificates and
// inserts them into a certdb, optionally also creating an OCSP
// response for them. If so, it will also return the OCSP response as
// a base64 encoded string.
// A Handler accepts new SSL certificates and inserts them into the
// certdb, creating an appropriate OCSP response for them.
type Handler struct {
dbAccessor certdb.Accessor
signer ocsp.Signer
}
// NewHandler creates a new Handler from a certdb.Accessor and ocsp.Signer
func NewHandler(dbAccessor certdb.Accessor, signer ocsp.Signer) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
signer: signer,
},
Methods: []string{"POST"},
}
}
// AddRequest describes a request from a client to insert a
// certificate into the database.
type AddRequest struct {
Serial string `json:"serial_number"`
AKI string `json:"authority_key_identifier"`
CALabel string `json:"ca_label"`
Status string `json:"status"`
Reason int `json:"reason"`
Expiry time.Time `json:"expiry"`
RevokedAt time.Time `json:"revoked_at"`
PEM string `json:"pem"`
}
// Map of valid reason codes
var validReasons = map[int]bool{
stdocsp.Unspecified: true,
stdocsp.KeyCompromise: true,
stdocsp.CACompromise: true,
stdocsp.AffiliationChanged: true,
stdocsp.Superseded: true,
stdocsp.CessationOfOperation: true,
stdocsp.CertificateHold: true,
stdocsp.RemoveFromCRL: true,
stdocsp.PrivilegeWithdrawn: true,
stdocsp.AACompromise: true,
}
// Handle handles HTTP requests to add certificates
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
var req AddRequest
err = json.Unmarshal(body, &req)
if err != nil {
return errors.NewBadRequestString("Unable to parse certificate addition request")
}
if len(req.Serial) == 0 {
return errors.NewBadRequestString("Serial number is required but not provided")
}
if len(req.AKI) == 0 {
return errors.NewBadRequestString("Authority key identifier is required but not provided")
}
if _, present := ocsp.StatusCode[req.Status]; !present {
return errors.NewBadRequestString("Invalid certificate status")
}
if ocsp.StatusCode[req.Status] == stdocsp.Revoked {
if req.RevokedAt == (time.Time{}) {
return errors.NewBadRequestString("Revoked certificate should specify when it was revoked")
}
if _, present := validReasons[req.Reason]; !present {
return errors.NewBadRequestString("Invalid certificate status reason code")
}
}
if len(req.PEM) == 0 {
return errors.NewBadRequestString("The provided certificate is empty")
}
// Parse the certificate and validate that it matches
cert, err := helpers.ParseCertificatePEM([]byte(req.PEM))
if err != nil {
return errors.NewBadRequestString("Unable to parse PEM encoded certificates")
}
serialBigInt := new(big.Int)
if _, success := serialBigInt.SetString(req.Serial, 16); !success {
return errors.NewBadRequestString("Unable to parse serial key of request")
}
if serialBigInt.Cmp(cert.SerialNumber) != 0 {
return errors.NewBadRequestString("Serial key of request and certificate do not match")
}
aki, err := hex.DecodeString(req.AKI)
if err != nil {
return errors.NewBadRequestString("Unable to decode authority key identifier")
}
if !bytes.Equal(aki, cert.AuthorityKeyId) {
return errors.NewBadRequestString("Authority key identifier of request and certificate do not match")
}
cr := certdb.CertificateRecord{
Serial: req.Serial,
AKI: req.AKI,
CALabel: req.CALabel,
Status: req.Status,
Reason: req.Reason,
Expiry: req.Expiry,
RevokedAt: req.RevokedAt,
PEM: req.PEM,
}
err = h.dbAccessor.InsertCertificate(cr)
if err != nil {
return err
}
result := map[string]string{}
if h.signer != nil {
// Now create an appropriate OCSP response
sr := ocsp.SignRequest{
Certificate: cert,
Status: req.Status,
Reason: req.Reason,
RevokedAt: req.RevokedAt,
}
ocspResponse, err := h.signer.Sign(sr)
if err != nil {
return err
}
// We parse the OCSP repsonse in order to get the next
// update time/expiry time
ocspParsed, err := stdocsp.ParseResponse(ocspResponse, nil)
if err != nil {
return err
}
result["ocsp_response"] = base64.StdEncoding.EncodeToString(ocspResponse)
ocspRecord := certdb.OCSPRecord{
Serial: req.Serial,
AKI: req.AKI,
Body: string(ocspResponse),
Expiry: ocspParsed.NextUpdate,
}
if err = h.dbAccessor.InsertOCSP(ocspRecord); err != nil {
return err
}
}
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,491 @@
package certadd
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/json"
"encoding/pem"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/ocsp"
"encoding/base64"
stdocsp "golang.org/x/crypto/ocsp"
)
func prepDB() (certdb.Accessor, error) {
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
dbAccessor := sql.NewAccessor(db)
return dbAccessor, nil
}
func makeRequest(t *testing.T, dbAccessor certdb.Accessor, signer ocsp.Signer, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := httptest.NewServer(NewHandler(dbAccessor, signer))
defer ts.Close()
blob, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func makeCertificate() (serialNumber *big.Int, cert *x509.Certificate, pemBytes []byte, signer ocsp.Signer, err error) {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
}
serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err = rand.Int(rand.Reader, serialNumberRange)
if err != nil {
return
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
}
cert = &template
issuerSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
return
}
responderSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
return
}
// Generate a CA certificate
issuerTemplate := x509.Certificate{
SerialNumber: issuerSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
IsCA: true,
BasicConstraintsValid: true,
}
issuerBytes, err := x509.CreateCertificate(rand.Reader, &issuerTemplate, &issuerTemplate, &privKey.PublicKey, privKey)
if err != nil {
return
}
issuer, err := x509.ParseCertificate(issuerBytes)
if err != nil {
return
}
responderTemplate := x509.Certificate{
SerialNumber: responderSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152 Responder"},
},
AuthorityKeyId: []byte{42, 42, 42, 43},
}
responderBytes, err := x509.CreateCertificate(rand.Reader, &responderTemplate, &responderTemplate, &privKey.PublicKey, privKey)
if err != nil {
return
}
responder, err := x509.ParseCertificate(responderBytes)
if err != nil {
return
}
signer, err = ocsp.NewSigner(issuer, responder, privKey, time.Hour)
if err != nil {
return
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, issuer, &privKey.PublicKey, privKey)
if err != nil {
return
}
pemBytes = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: derBytes,
})
return
}
func TestInsertValidCertificate(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusOK {
t.Fatal("Expected HTTP OK, got", resp.StatusCode, string(body))
}
var response map[string]interface{}
if err = json.Unmarshal(body, &response); err != nil {
t.Fatal("Could not parse response: ", err)
}
responseResult := response["result"].(map[string]interface{})
encodedOcsp := responseResult["ocsp_response"].(string)
rawOcsp, err := base64.StdEncoding.DecodeString(encodedOcsp)
if err != nil {
t.Fatal("Could not base64 decode response: ", err)
}
returnedOcsp, err := stdocsp.ParseResponse(rawOcsp, nil)
if err != nil {
t.Fatal("Could not parse returned OCSP response", err)
}
ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(16), hex.EncodeToString(cert.AuthorityKeyId))
if err != nil {
t.Fatal(err)
}
if len(ocsps) != 1 {
t.Fatal("Expected 1 OCSP record to be inserted, but found ", len(ocsps))
}
parsedOcsp, err := stdocsp.ParseResponse([]byte(ocsps[0].Body), nil)
if err != nil {
t.Fatal(err)
}
if parsedOcsp.SerialNumber.Cmp(returnedOcsp.SerialNumber) != 0 {
t.Fatal("The returned and inserted OCSP response have different serial numbers: got ", returnedOcsp.SerialNumber, " but decoded ", parsedOcsp.SerialNumber)
}
if parsedOcsp.SerialNumber.Cmp(serialNumber) != 0 {
t.Fatal("Got the wrong serial number: expected", serialNumber, "but got", parsedOcsp.SerialNumber)
}
if parsedOcsp.Status != stdocsp.Good {
t.Fatal("Expected OCSP response status to be ", stdocsp.Good,
" but found ", parsedOcsp.Status)
}
}
func TestInsertMissingSerial(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
_, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertMissingAKI(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, _, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertMissingPEM(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, _, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertInvalidSerial(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
_, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": "this is not a serial number",
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertInvalidAKI(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, _, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": "this is not an AKI",
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request, got", resp.StatusCode, string(body))
}
}
func TestInsertInvalidStatus(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "invalid",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertInvalidPEM(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, _, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": "this is not a PEM certificate",
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request, got", resp.StatusCode, string(body))
}
}
func TestInsertWrongSerial(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
_, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": big.NewInt(1).Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertWrongAKI(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, _, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString([]byte{7, 7}),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertRevokedCertificate(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "revoked",
"pem": string(pemBytes),
"revoked_at": time.Now(),
})
if resp.StatusCode != http.StatusOK {
t.Fatal("Expected HTTP OK", resp.StatusCode, string(body))
}
ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(16), hex.EncodeToString(cert.AuthorityKeyId))
if err != nil {
t.Fatal(err)
}
if len(ocsps) != 1 {
t.Fatal("Expected 1 OCSP record to be inserted, but found ", len(ocsps))
}
response, err := stdocsp.ParseResponse([]byte(ocsps[0].Body), nil)
if err != nil {
t.Fatal(err)
}
if response.Status != stdocsp.Revoked {
t.Fatal("Expected OCSP response status to be ", stdocsp.Revoked,
" but found ", response.Status)
}
}
func TestInsertRevokedCertificateWithoutTime(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "revoked",
"pem": string(pemBytes),
// Omit RevokedAt
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}

View File

@@ -0,0 +1,50 @@
// Package certinfo implements the HTTP handler for the certinfo command.
package certinfo
import (
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certinfo"
"github.com/cloudflare/cfssl/log"
)
// Handler accepts requests for either remote or uploaded
// certificates to be bundled, and returns a certificate bundle (or
// error).
type Handler struct{}
// NewHandler creates a new bundler that uses the root bundle and
// intermediate bundle in the trust chain.
func NewHandler() http.Handler {
return api.HTTPHandler{Handler: new(Handler), Methods: []string{"POST"}}
}
// Handle implements an http.Handler interface for the bundle handler.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) (err error) {
blob, matched, err := api.ProcessRequestFirstMatchOf(r,
[][]string{
{"certificate"},
{"domain"},
})
if err != nil {
log.Warningf("invalid request: %v", err)
return err
}
var cert *certinfo.Certificate
switch matched[0] {
case "domain":
if cert, err = certinfo.ParseCertificateDomain(blob["domain"]); err != nil {
log.Warningf("couldn't parse remote certificate: %v", err)
return err
}
case "certificate":
if cert, err = certinfo.ParseCertificatePEM([]byte(blob["certificate"])); err != nil {
log.Warningf("bad PEM certifcate: %v", err)
return err
}
}
return api.SendResponse(w, cert)
}

6
vendor/github.com/cloudflare/cfssl/api/client/api.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
package client
// SignResult is the result of signing a CSR.
type SignResult struct {
Certificate []byte `json:"certificate"`
}

346
vendor/github.com/cloudflare/cfssl/api/client/client.go generated vendored Normal file
View File

@@ -0,0 +1,346 @@
// Package client implements the a Go client for CFSSL API commands.
package client
import (
"bytes"
"crypto/tls"
"encoding/json"
stderr "errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/info"
"github.com/cloudflare/cfssl/log"
)
// A server points to a single remote CFSSL instance.
type server struct {
URL string
TLSConfig *tls.Config
reqModifier func(*http.Request, []byte)
RequestTimeout time.Duration
}
// A Remote points to at least one (but possibly multiple) remote
// CFSSL instances. It must be able to perform a authenticated and
// unauthenticated certificate signing requests, return information
// about the CA on the other end, and return a list of the hosts that
// are used by the remote.
type Remote interface {
AuthSign(req, id []byte, provider auth.Provider) ([]byte, error)
Sign(jsonData []byte) ([]byte, error)
Info(jsonData []byte) (*info.Resp, error)
Hosts() []string
SetReqModifier(func(*http.Request, []byte))
SetRequestTimeout(d time.Duration)
}
// NewServer sets up a new server target. The address should be of
// The format [protocol:]name[:port] of the remote CFSSL instance.
// If no protocol is given http is default. If no port
// is specified, the CFSSL default port (8888) is used. If the name is
// a comma-separated list of hosts, an ordered group will be returned.
func NewServer(addr string) Remote {
return NewServerTLS(addr, nil)
}
// NewServerTLS is the TLS version of NewServer
func NewServerTLS(addr string, tlsConfig *tls.Config) Remote {
addrs := strings.Split(addr, ",")
var remote Remote
if len(addrs) > 1 {
remote, _ = NewGroup(addrs, tlsConfig, StrategyOrderedList)
} else {
u, err := normalizeURL(addrs[0])
if err != nil {
log.Errorf("bad url: %v", err)
return nil
}
srv := newServer(u, tlsConfig)
if srv != nil {
remote = srv
}
}
return remote
}
func (srv *server) Hosts() []string {
return []string{srv.URL}
}
func (srv *server) SetReqModifier(mod func(*http.Request, []byte)) {
srv.reqModifier = mod
}
func (srv *server) SetRequestTimeout(timeout time.Duration) {
srv.RequestTimeout = timeout
}
func newServer(u *url.URL, tlsConfig *tls.Config) *server {
URL := u.String()
return &server{
URL: URL,
TLSConfig: tlsConfig,
}
}
func (srv *server) getURL(endpoint string) string {
return fmt.Sprintf("%s/api/v1/cfssl/%s", srv.URL, endpoint)
}
func (srv *server) createTLSTransport() (transport *http.Transport) {
// Setup HTTPS client
tlsConfig := srv.TLSConfig
tlsConfig.BuildNameToCertificate()
return &http.Transport{TLSClientConfig: tlsConfig}
}
// post connects to the remote server and returns a Response struct
func (srv *server) post(url string, jsonData []byte) (*api.Response, error) {
var resp *http.Response
var err error
client := &http.Client{}
if srv.TLSConfig != nil {
client.Transport = srv.createTLSTransport()
}
if srv.RequestTimeout != 0 {
client.Timeout = srv.RequestTimeout
}
req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData))
if err != nil {
err = fmt.Errorf("failed POST to %s: %v", url, err)
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, err)
}
req.Close = true
req.Header.Set("content-type", "application/json")
if srv.reqModifier != nil {
srv.reqModifier(req, jsonData)
}
resp, err = client.Do(req)
if err != nil {
err = fmt.Errorf("failed POST to %s: %v", url, err)
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, err)
}
defer req.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.IOError, err)
}
if resp.StatusCode != http.StatusOK {
log.Errorf("http error with %s", url)
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New(string(body)))
}
var response api.Response
err = json.Unmarshal(body, &response)
if err != nil {
log.Debug("Unable to parse response body:", string(body))
return nil, errors.Wrap(errors.APIClientError, errors.JSONError, err)
}
if !response.Success || response.Result == nil {
if len(response.Errors) > 0 {
return nil, errors.Wrap(errors.APIClientError, errors.ServerRequestFailed, stderr.New(response.Errors[0].Message))
}
return nil, errors.New(errors.APIClientError, errors.ServerRequestFailed)
}
return &response, nil
}
// AuthSign fills out an authenticated signing request to the server,
// receiving a certificate or error in response.
// It takes the serialized JSON request to send, remote address and
// authentication provider.
func (srv *server) AuthSign(req, id []byte, provider auth.Provider) ([]byte, error) {
return srv.authReq(req, id, provider, "sign")
}
// AuthInfo fills out an authenticated info request to the server,
// receiving a certificate or error in response.
// It takes the serialized JSON request to send, remote address and
// authentication provider.
func (srv *server) AuthInfo(req, id []byte, provider auth.Provider) ([]byte, error) {
return srv.authReq(req, id, provider, "info")
}
// authReq is the common logic for AuthSign and AuthInfo -- perform the given
// request, and return the resultant certificate.
// The target is either 'sign' or 'info'.
func (srv *server) authReq(req, ID []byte, provider auth.Provider, target string) ([]byte, error) {
url := srv.getURL("auth" + target)
token, err := provider.Token(req)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.AuthenticationFailure, err)
}
aReq := &auth.AuthenticatedRequest{
Timestamp: time.Now().Unix(),
RemoteAddress: ID,
Token: token,
Request: req,
}
jsonData, err := json.Marshal(aReq)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.JSONError, err)
}
response, err := srv.post(url, jsonData)
if err != nil {
return nil, err
}
result, ok := response.Result.(map[string]interface{})
if !ok {
return nil, errors.New(errors.APIClientError, errors.JSONError)
}
cert, ok := result["certificate"].(string)
if !ok {
return nil, errors.New(errors.APIClientError, errors.JSONError)
}
return []byte(cert), nil
}
// Sign sends a signature request to the remote CFSSL server,
// receiving a signed certificate or an error in response.
// It takes the serialized JSON request to send.
func (srv *server) Sign(jsonData []byte) ([]byte, error) {
return srv.request(jsonData, "sign")
}
// Info sends an info request to the remote CFSSL server, receiving a
// response or an error in response.
// It takes the serialized JSON request to send.
func (srv *server) Info(jsonData []byte) (*info.Resp, error) {
res, err := srv.getResultMap(jsonData, "info")
if err != nil {
return nil, err
}
info := new(info.Resp)
if val, ok := res["certificate"]; ok {
info.Certificate = val.(string)
}
var usages []interface{}
if val, ok := res["usages"]; ok && val != nil {
usages = val.([]interface{})
}
if val, ok := res["expiry"]; ok && val != nil {
info.ExpiryString = val.(string)
}
info.Usage = make([]string, len(usages))
for i, s := range usages {
info.Usage[i] = s.(string)
}
return info, nil
}
func (srv *server) getResultMap(jsonData []byte, target string) (result map[string]interface{}, err error) {
url := srv.getURL(target)
response, err := srv.post(url, jsonData)
if err != nil {
return
}
result, ok := response.Result.(map[string]interface{})
if !ok {
err = errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New("response is formatted improperly"))
return
}
return
}
// request performs the common logic for Sign and Info, performing the actual
// request and returning the resultant certificate.
func (srv *server) request(jsonData []byte, target string) ([]byte, error) {
result, err := srv.getResultMap(jsonData, target)
if err != nil {
return nil, err
}
cert := result["certificate"].(string)
if cert != "" {
return []byte(cert), nil
}
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New("response doesn't contain certificate."))
}
// AuthRemote acts as a Remote with a default Provider for AuthSign.
type AuthRemote struct {
Remote
provider auth.Provider
}
// NewAuthServer sets up a new auth server target with an addr
// in the same format at NewServer and a default authentication provider to
// use for Sign requests.
func NewAuthServer(addr string, tlsConfig *tls.Config, provider auth.Provider) *AuthRemote {
return &AuthRemote{
Remote: NewServerTLS(addr, tlsConfig),
provider: provider,
}
}
// Sign is overloaded to perform an AuthSign request using the default auth provider.
func (ar *AuthRemote) Sign(req []byte) ([]byte, error) {
return ar.AuthSign(req, nil, ar.provider)
}
// nomalizeURL checks for http/https protocol, appends "http" as default protocol if not defiend in url
func normalizeURL(addr string) (*url.URL, error) {
addr = strings.TrimSpace(addr)
u, err := url.Parse(addr)
if err != nil {
return nil, err
}
if u.Opaque != "" {
u.Host = net.JoinHostPort(u.Scheme, u.Opaque)
u.Opaque = ""
} else if u.Path != "" && !strings.Contains(u.Path, ":") {
u.Host = net.JoinHostPort(u.Path, "8888")
u.Path = ""
} else if u.Scheme == "" {
u.Host = u.Path
u.Path = ""
}
if u.Scheme != "https" {
u.Scheme = "http"
}
_, port, err := net.SplitHostPort(u.Host)
if err != nil {
_, port, err = net.SplitHostPort(u.Host + ":8888")
if err != nil {
return nil, err
}
}
if port != "" {
_, err = strconv.Atoi(port)
if err != nil {
return nil, err
}
}
return u, nil
}

View File

@@ -0,0 +1,203 @@
package client
import (
"crypto/tls"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/helpers"
"net"
"strings"
"testing"
)
var (
testProvider auth.Provider
testKey = "0123456789ABCDEF0123456789ABCDEF"
testAD = []byte{1, 2, 3, 4} // IP address 1.2.3.4
)
func TestNewServer(t *testing.T) {
s := NewServer("1.1.1.1:::123456789")
if s != nil {
t.Fatalf("fatal error, server created with too many colons %v", s)
}
s2 := NewServer("1.1.1.1:[]")
if s != nil {
t.Fatalf("%v", s2)
}
_, port, _ := net.SplitHostPort("")
if port != "" {
t.Fatalf("%v", port)
}
s = NewServer("http://127.0.0.1:8888")
hosts := s.Hosts()
if len(hosts) != 1 || hosts[0] != "http://127.0.0.1:8888" {
t.Fatalf("expected [http://127.0.0.1:8888], but have %v", hosts)
}
s = NewServer("http://1.1.1.1:9999")
hosts = s.Hosts()
if len(hosts) != 1 || hosts[0] != "http://1.1.1.1:9999" {
t.Fatalf("expected [http://1.1.1.1:9999], but have %v", hosts)
}
s = NewServer("https://1.1.1.1:8080")
hosts = s.Hosts()
if len(hosts) != 1 || hosts[0] != "https://1.1.1.1:8080" {
t.Fatalf("expected [https://1.1.1.1:8080], but have %v", hosts)
}
}
func TestInvalidPort(t *testing.T) {
s := NewServer("1.1.1.1:99999999999999999999999999999")
if s != nil {
t.Fatalf("%v", s)
}
}
func TestAuthSign(t *testing.T) {
s := NewServer(".X")
testProvider, _ = auth.New(testKey, nil)
testRequest := []byte(`testing 1 2 3`)
as, err := s.AuthSign(testRequest, testAD, testProvider)
if as != nil || err == nil {
t.Fatal("expected error with auth sign function")
}
}
func TestDefaultAuthSign(t *testing.T) {
testProvider, _ = auth.New(testKey, nil)
s := NewAuthServer(".X", nil, testProvider)
testRequest := []byte(`testing 1 2 3`)
as, err := s.Sign(testRequest)
if as != nil || err == nil {
t.Fatal("expected error with auth sign function")
}
}
func TestSign(t *testing.T) {
s := NewServer(".X")
sign, err := s.Sign([]byte{5, 5, 5, 5})
if sign != nil || err == nil {
t.Fatalf("expected error with sign function")
}
}
func TestNewMutualTLSServer(t *testing.T) {
cert, _ := helpers.LoadClientCertificate("../../helpers/testdata/ca.pem", "../../helpers/testdata/ca_key.pem")
s := NewServerTLS("https://nohost:8888", helpers.CreateTLSConfig(nil, cert))
if s == nil {
t.Fatalf("fatal error, empty server")
}
_, err := s.Sign([]byte{5, 5, 5, 5})
if err == nil {
t.Fatalf("expected error with sign function")
}
if !strings.Contains(err.Error(), "Post https://nohost:8888/api/v1/cfssl/sign: dial tcp: lookup nohost") {
t.Fatalf("no error message %v", err)
}
}
func TestNewServerGroup(t *testing.T) {
s := NewServer("cfssl1.local:8888, cfssl2.local:8888, http://cfssl3.local:8888, http://cfssl4.local:8888")
ogl, ok := s.(*orderedListGroup)
if !ok {
t.Fatalf("expected NewServer to return an ordered group list with a list of servers, instead got a %T = %+v", ogl, ogl)
}
if len(ogl.remotes) != 4 {
t.Fatalf("expected the remote to have four servers, but it has %d", len(ogl.remotes))
}
hosts := ogl.Hosts()
if len(hosts) != 4 {
t.Fatalf("expected 2 hosts in the group, but have %d", len(hosts))
}
if hosts[0] != "http://cfssl1.local:8888" {
t.Fatalf("expected to see http://cfssl1.local:8888, but saw %s",
hosts[0])
}
if hosts[1] != "http://cfssl2.local:8888" {
t.Fatalf("expected to see http://cfssl2.local:8888, but saw %s",
hosts[1])
}
if hosts[2] != "http://cfssl3.local:8888" {
t.Fatalf("expected to see http://cfssl1.local:8888, but saw %s",
hosts[2])
}
if hosts[3] != "http://cfssl4.local:8888" {
t.Fatalf("expected to see http://cfssl2.local:8888, but saw %s",
hosts[3])
}
}
func TestNewTLSServerGroup(t *testing.T) {
NewTLSServerGroup(t, nil)
}
func TestNewMutualTLSServerGroup(t *testing.T) {
cert, _ := helpers.LoadClientCertificate("../../helpers/testdata/ca.pem", "../../helpers/testdata/ca_key.pem")
NewTLSServerGroup(t, cert)
}
func NewTLSServerGroup(t *testing.T, cert *tls.Certificate) {
s := NewServerTLS("https://cfssl1.local:8888, https://cfssl2.local:8888", helpers.CreateTLSConfig(nil, cert))
ogl, ok := s.(*orderedListGroup)
if !ok {
t.Fatalf("expected NewServer to return an ordered group list with a list of servers, instead got a %T = %+v", ogl, ogl)
}
if len(ogl.remotes) != 2 {
t.Fatalf("expected the remote to have two servers, but it has %d", len(ogl.remotes))
}
hosts := ogl.Hosts()
if len(hosts) != 2 {
t.Fatalf("expected 2 hosts in the group, but have %d", len(hosts))
}
if hosts[0] != "https://cfssl1.local:8888" {
t.Fatalf("expected to see https://cfssl1.local:8888, but saw %s",
hosts[0])
}
if hosts[1] != "https://cfssl2.local:8888" {
t.Fatalf("expected to see https://cfssl2.local:8888, but saw %s",
hosts[1])
}
}
func TestNewOGLGroup(t *testing.T) {
strategy := StrategyFromString("ordered_list")
if strategy == StrategyInvalid {
t.Fatal("expected StrategyOrderedList as selected strategy but have StrategyInvalid")
}
if strategy != StrategyOrderedList {
t.Fatalf("expected StrategyOrderedList (%d) but have %d", StrategyOrderedList, strategy)
}
rem, err := NewGroup([]string{"ca1.local,", "ca2.local"}, nil, strategy)
if err != nil {
t.Fatalf("%v", err)
}
ogl, ok := rem.(*orderedListGroup)
if !ok {
t.Fatalf("expected to get an orderedListGroup but got %T", rem)
}
if len(ogl.remotes) != 2 {
t.Fatalf("expected two remotes in the ordered group list but have %d", len(ogl.remotes))
}
}

125
vendor/github.com/cloudflare/cfssl/api/client/group.go generated vendored Normal file
View File

@@ -0,0 +1,125 @@
package client
import (
"crypto/tls"
"errors"
"net/http"
"strings"
"time"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/info"
)
// Strategy is the means by which the server to use as a remote should
// be selected.
type Strategy int
const (
// StrategyInvalid indicates any strategy that is unsupported
// or returned when no strategy is applicable.
StrategyInvalid = iota
// StrategyOrderedList is a sequential list of servers: if the
// first server cannot be reached, the next is used. The
// client will proceed in this manner until the list of
// servers is exhausted, and then an error is returned.
StrategyOrderedList
)
var strategyStrings = map[string]Strategy{
"ordered_list": StrategyOrderedList,
}
// StrategyFromString takes a string describing a
func StrategyFromString(s string) Strategy {
s = strings.TrimSpace(strings.ToLower(s))
strategy, ok := strategyStrings[s]
if !ok {
return StrategyInvalid
}
return strategy
}
// NewGroup will use the collection of remotes specified with the
// given strategy.
func NewGroup(remotes []string, tlsConfig *tls.Config, strategy Strategy) (Remote, error) {
var servers = make([]*server, len(remotes))
for i := range remotes {
u, err := normalizeURL(remotes[i])
if err != nil {
return nil, err
}
servers[i] = newServer(u, tlsConfig)
}
switch strategy {
case StrategyOrderedList:
return newOrdererdListGroup(servers)
default:
return nil, errors.New("unrecognised strategy")
}
}
type orderedListGroup struct {
remotes []*server
}
func (g *orderedListGroup) Hosts() []string {
var hosts = make([]string, 0, len(g.remotes))
for _, srv := range g.remotes {
srvHosts := srv.Hosts()
hosts = append(hosts, srvHosts[0])
}
return hosts
}
func (g *orderedListGroup) SetRequestTimeout(timeout time.Duration) {
for _, srv := range g.remotes {
srv.SetRequestTimeout(timeout)
}
}
func newOrdererdListGroup(remotes []*server) (Remote, error) {
return &orderedListGroup{
remotes: remotes,
}, nil
}
func (g *orderedListGroup) AuthSign(req, id []byte, provider auth.Provider) (resp []byte, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].AuthSign(req, id, provider)
if err == nil {
return resp, nil
}
}
return nil, err
}
func (g *orderedListGroup) Sign(jsonData []byte) (resp []byte, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].Sign(jsonData)
if err == nil {
return resp, nil
}
}
return nil, err
}
func (g *orderedListGroup) Info(jsonData []byte) (resp *info.Resp, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].Info(jsonData)
if err == nil {
return resp, nil
}
}
return nil, err
}
// SetReqModifier does nothing because there is no request modifier for group
func (g *orderedListGroup) SetReqModifier(mod func(*http.Request, []byte)) {
// noop
}

93
vendor/github.com/cloudflare/cfssl/api/crl/crl.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
// Package crl implements the HTTP handler for the crl command.
package crl
import (
"crypto"
"crypto/x509"
"net/http"
"os"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/crl"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
)
// A Handler accepts requests with a serial number parameter
// and revokes
type Handler struct {
dbAccessor certdb.Accessor
ca *x509.Certificate
key crypto.Signer
}
// NewHandler returns a new http.Handler that handles a revoke request.
func NewHandler(dbAccessor certdb.Accessor, caPath string, caKeyPath string) (http.Handler, error) {
ca, err := helpers.ReadBytes(caPath)
if err != nil {
return nil, err
}
caKey, err := helpers.ReadBytes(caKeyPath)
if err != nil {
return nil, errors.Wrap(errors.PrivateKeyError, errors.ReadFailed, err)
}
// Parse the PEM encoded certificate
issuerCert, err := helpers.ParseCertificatePEM(ca)
if err != nil {
return nil, err
}
strPassword := os.Getenv("CFSSL_CA_PK_PASSWORD")
password := []byte(strPassword)
if strPassword == "" {
password = nil
}
// Parse the key given
key, err := helpers.ParsePrivateKeyPEMWithPassword(caKey, password)
if err != nil {
log.Debug("malformed private key %v", err)
return nil, err
}
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
ca: issuerCert,
key: key,
},
Methods: []string{"GET"},
}, nil
}
// Handle responds to revocation requests. It attempts to revoke
// a certificate with a given serial number
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
var newExpiryTime = 7 * helpers.OneDay
certs, err := h.dbAccessor.GetRevokedAndUnexpiredCertificates()
if err != nil {
return err
}
queryExpiryTime := r.URL.Query().Get("expiry")
if queryExpiryTime != "" {
log.Infof("requested expiry time of %s", queryExpiryTime)
newExpiryTime, err = time.ParseDuration(queryExpiryTime)
if err != nil {
return err
}
}
result, err := crl.NewCRLFromDB(certs, h.ca, h.key, newExpiryTime)
if err != nil {
return err
}
return api.SendResponse(w, result)
}

149
vendor/github.com/cloudflare/cfssl/api/crl/crl_test.go generated vendored Normal file
View File

@@ -0,0 +1,149 @@
package crl
import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/helpers"
)
const (
fakeAKI = "fake aki"
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
)
func prepDB() (certdb.Accessor, error) {
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
expirationTime := time.Now().AddDate(1, 0, 0)
var cert = certdb.CertificateRecord{
Serial: "1",
AKI: fakeAKI,
Expiry: expirationTime,
PEM: "revoked cert",
Status: "revoked",
RevokedAt: time.Now(),
Reason: 4,
}
dbAccessor := sql.NewAccessor(db)
err := dbAccessor.InsertCertificate(cert)
if err != nil {
return nil, err
}
return dbAccessor, nil
}
func testGetCRL(t *testing.T, dbAccessor certdb.Accessor, expiry string) (resp *http.Response, body []byte) {
handler, err := NewHandler(dbAccessor, testCaFile, testCaKeyFile)
if err != nil {
t.Fatal(err)
}
ts := httptest.NewServer(handler)
defer ts.Close()
if expiry != "" {
resp, err = http.Get(ts.URL + "?expiry=" + expiry)
} else {
resp, err = http.Get(ts.URL)
}
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func TestCRLGeneration(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, body := testGetCRL(t, dbAccessor, "")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
crlBytes := message.Result.(string)
crlBytesDER, err := base64.StdEncoding.DecodeString(crlBytes)
if err != nil {
t.Fatal("failed to decode certificate ", err)
}
parsedCrl, err := x509.ParseCRL(crlBytesDER)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if parsedCrl.HasExpired(time.Now().Add(5 * helpers.OneDay)) {
t.Fatal("the request will expire after 5 days, this shouldn't happen")
}
certs := parsedCrl.TBSCertList.RevokedCertificates
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.SerialNumber.String() != "1" {
t.Fatal("cert was not correctly inserted in CRL, serial was ", cert.SerialNumber)
}
}
func TestCRLGenerationWithExpiry(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, body := testGetCRL(t, dbAccessor, "119h")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
crlBytes := message.Result.(string)
crlBytesDER, err := base64.StdEncoding.DecodeString(crlBytes)
if err != nil {
t.Fatal("failed to decode certificate ", err)
}
parsedCrl, err := x509.ParseCRL(crlBytesDER)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if !parsedCrl.HasExpired(time.Now().Add(5 * helpers.OneDay)) {
t.Fatal("the request should have expired")
}
certs := parsedCrl.TBSCertList.RevokedCertificates
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.SerialNumber.String() != "1" {
t.Fatal("cert was not correctly inserted in CRL, serial was ", cert.SerialNumber)
}
}

102
vendor/github.com/cloudflare/cfssl/api/gencrl/gencrl.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
// Package gencrl implements the HTTP handler for the gencrl commands.
package gencrl
import (
"crypto/rand"
"crypto/x509/pkix"
"encoding/json"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
"io/ioutil"
"math/big"
"net/http"
"strconv"
"strings"
"time"
)
// This type is meant to be unmarshalled from JSON
type jsonCRLRequest struct {
Certificate string `json:"certificate"`
SerialNumber []string `json:"serialNumber"`
PrivateKey string `json:"issuingKey"`
ExpiryTime string `json:"expireTime"`
}
// Handle responds to requests for crl generation. It creates this crl
// based off of the given certificate, serial numbers, and private key
func gencrlHandler(w http.ResponseWriter, r *http.Request) error {
var revokedCerts []pkix.RevokedCertificate
var oneWeek = time.Duration(604800) * time.Second
var newExpiryTime = time.Now()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
req := &jsonCRLRequest{}
err = json.Unmarshal(body, req)
if err != nil {
log.Error(err)
}
if req.ExpiryTime != "" {
expiryTime := strings.TrimSpace(req.ExpiryTime)
expiryInt, err := strconv.ParseInt(expiryTime, 0, 32)
if err != nil {
return err
}
newExpiryTime = time.Now().Add((time.Duration(expiryInt) * time.Second))
}
if req.ExpiryTime == "" {
newExpiryTime = time.Now().Add(oneWeek)
}
if err != nil {
return err
}
cert, err := helpers.ParseCertificatePEM([]byte(req.Certificate))
if err != nil {
log.Error("error from ParseCertificatePEM", err)
return errors.NewBadRequestString("malformed certificate")
}
for _, value := range req.SerialNumber {
tempBigInt := new(big.Int)
tempBigInt.SetString(value, 10)
tempCert := pkix.RevokedCertificate{
SerialNumber: tempBigInt,
RevocationTime: time.Now(),
}
revokedCerts = append(revokedCerts, tempCert)
}
key, err := helpers.ParsePrivateKeyPEM([]byte(req.PrivateKey))
if err != nil {
log.Debug("malformed private key %v", err)
return errors.NewBadRequestString("malformed Private Key")
}
result, err := cert.CreateCRL(rand.Reader, key, revokedCerts, time.Now(), newExpiryTime)
if err != nil {
log.Debug("unable to create CRL: %v", err)
return err
}
return api.SendResponse(w, result)
}
// NewHandler returns a new http.Handler that handles a crl generation request.
func NewHandler() http.Handler {
return api.HTTPHandler{
Handler: api.HandlerFunc(gencrlHandler),
Methods: []string{"POST"},
}
}

View File

@@ -0,0 +1,107 @@
package gencrl
import (
"bytes"
"encoding/json"
"github.com/cloudflare/cfssl/api"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
const (
cert = "../../crl/testdata/caTwo.pem"
key = "../../crl/testdata/ca-keyTwo.pem"
serialList = "../../crl/testdata/serialList"
expiryTime = "2000"
)
type testJSON struct {
Certificate string
SerialNumber []string
PrivateKey string
ExpiryTime string
ExpectedHTTPStatus int
ExpectedSuccess bool
}
var tester = testJSON{
Certificate: cert,
SerialNumber: []string{"1", "2", "3"},
PrivateKey: key,
ExpiryTime: "2000",
ExpectedHTTPStatus: 200,
ExpectedSuccess: true,
}
func newTestHandler(t *testing.T) http.Handler {
return NewHandler()
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func newCRLServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testCRLCreation(t *testing.T, issuingKey, certFile string, expiry string, serialList []string) (resp *http.Response, body []byte) {
ts := newCRLServer(t)
defer ts.Close()
obj := map[string]interface{}{}
if certFile != "" {
c, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
obj["certificate"] = string(c)
}
obj["serialNumber"] = serialList
if issuingKey != "" {
c, err := ioutil.ReadFile(issuingKey)
if err != nil {
t.Fatal(err)
}
obj["issuingKey"] = string(c)
}
obj["expireTime"] = expiry
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func TestCRL(t *testing.T) {
resp, body := testCRLCreation(t, tester.PrivateKey, tester.Certificate, tester.ExpiryTime, tester.SerialNumber)
if resp.StatusCode != tester.ExpectedHTTPStatus {
t.Logf("expected: %d, have %d", tester.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, tester.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, tester.ExpectedHTTPStatus, message)
}
}

View File

@@ -0,0 +1,319 @@
// Package generator implements the HTTP handlers for certificate generation.
package generator
import (
"crypto/md5"
"crypto/sha1"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/bundler"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/universal"
)
const (
// CSRNoHostMessage is used to alert the user to a certificate lacking a hosts field.
CSRNoHostMessage = `This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").`
// NoBundlerMessage is used to alert the user that the server does not have a bundler initialized.
NoBundlerMessage = `This request requires a bundler, but one is not initialized for the API server.`
)
// Sum contains digests for a certificate or certificate request.
type Sum struct {
MD5 string `json:"md5"`
SHA1 string `json:"sha-1"`
}
// Validator is a type of function that contains the logic for validating
// a certificate request.
type Validator func(*csr.CertificateRequest) error
// A CertRequest stores a PEM-encoded private key and corresponding
// CSR; this is returned from the CSR generation endpoint.
type CertRequest struct {
Key string `json:"private_key"`
CSR string `json:"certificate_request"`
Sums map[string]Sum `json:"sums"`
}
// A Handler accepts JSON-encoded certificate requests and
// returns a new private key and certificate request.
type Handler struct {
generator *csr.Generator
}
// NewHandler builds a new Handler from the
// validation function provided.
func NewHandler(validator Validator) (http.Handler, error) {
log.Info("setting up key / CSR generator")
return &api.HTTPHandler{
Handler: &Handler{
generator: &csr.Generator{Validator: validator},
},
Methods: []string{"POST"},
}, nil
}
func computeSum(in []byte) (sum Sum, err error) {
var data []byte
p, _ := pem.Decode(in)
if p == nil {
err = errors.NewBadRequestString("not a CSR or certificate")
return
}
switch p.Type {
case "CERTIFICATE REQUEST":
var req *x509.CertificateRequest
req, err = x509.ParseCertificateRequest(p.Bytes)
if err != nil {
return
}
data = req.Raw
case "CERTIFICATE":
var cert *x509.Certificate
cert, err = x509.ParseCertificate(p.Bytes)
if err != nil {
return
}
data = cert.Raw
default:
err = errors.NewBadRequestString("not a CSR or certificate")
return
}
md5Sum := md5.Sum(data)
sha1Sum := sha1.Sum(data)
sum.MD5 = fmt.Sprintf("%X", md5Sum[:])
sum.SHA1 = fmt.Sprintf("%X", sha1Sum[:])
return
}
// Handle responds to requests for the CA to generate a new private
// key and certificate request on behalf of the client. The format for
// these requests is documented in the API documentation.
func (g *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("request for CSR")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
req := new(csr.CertificateRequest)
req.KeyRequest = csr.NewBasicKeyRequest()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
if req.CA != nil {
log.Warningf("request received with CA section")
return errors.NewBadRequestString("ca section only permitted in initca")
}
csr, key, err := g.generator.ProcessRequest(req)
if err != nil {
log.Warningf("failed to process CSR: %v", err)
// The validator returns a *cfssl/errors.HttpError
return err
}
sum, err := computeSum(csr)
if err != nil {
return errors.NewBadRequest(err)
}
// Both key and csr are returned PEM-encoded.
response := api.NewSuccessResponse(&CertRequest{
Key: string(key),
CSR: string(csr),
Sums: map[string]Sum{"certificate_request": sum},
})
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err = enc.Encode(response)
return err
}
// A CertGeneratorHandler accepts JSON-encoded certificate requests
// and returns a new private key and signed certificate; it handles
// sending the CSR to the server.
type CertGeneratorHandler struct {
generator *csr.Generator
bundler *bundler.Bundler
signer signer.Signer
}
// NewCertGeneratorHandler builds a new handler for generating
// certificates directly from certificate requests; the validator covers
// the certificate request and the CA's key and certificate are used to
// sign the generated request. If remote is not an empty string, the
// handler will send signature requests to the CFSSL instance contained
// in remote.
func NewCertGeneratorHandler(validator Validator, caFile, caKeyFile string, policy *config.Signing) (http.Handler, error) {
var err error
log.Info("setting up new generator / signer")
cg := new(CertGeneratorHandler)
if policy == nil {
policy = &config.Signing{
Default: config.DefaultConfig(),
Profiles: nil,
}
}
root := universal.Root{
Config: map[string]string{
"ca-file": caFile,
"ca-key-file": caKeyFile,
},
}
if cg.signer, err = universal.NewSigner(root, policy); err != nil {
log.Errorf("setting up signer failed: %v", err)
return nil, err
}
cg.generator = &csr.Generator{Validator: validator}
return api.HTTPHandler{Handler: cg, Methods: []string{"POST"}}, nil
}
// NewCertGeneratorHandlerFromSigner returns a handler directly from
// the signer and validation function.
func NewCertGeneratorHandlerFromSigner(validator Validator, signer signer.Signer) http.Handler {
return api.HTTPHandler{
Handler: &CertGeneratorHandler{
generator: &csr.Generator{Validator: validator},
signer: signer,
},
Methods: []string{"POST"},
}
}
// SetBundler allows injecting an optional Bundler into the CertGeneratorHandler.
func (cg *CertGeneratorHandler) SetBundler(caBundleFile, intBundleFile string) (err error) {
cg.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile)
return err
}
type genSignRequest struct {
Request *csr.CertificateRequest `json:"request"`
Profile string `json:"profile"`
Label string `json:"label"`
Bundle bool `json:"bundle"`
}
// Handle responds to requests for the CA to generate a new private
// key and certificate on behalf of the client. The format for these
// requests is documented in the API documentation.
func (cg *CertGeneratorHandler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("request for CSR")
req := new(genSignRequest)
req.Request = csr.New()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
if req.Request == nil {
log.Warning("empty request received")
return errors.NewBadRequestString("missing request section")
}
if req.Request.CA != nil {
log.Warningf("request received with CA section")
return errors.NewBadRequestString("ca section only permitted in initca")
}
csr, key, err := cg.generator.ProcessRequest(req.Request)
if err != nil {
log.Warningf("failed to process CSR: %v", err)
// The validator returns a *cfssl/errors.HttpError
return err
}
signReq := signer.SignRequest{
Request: string(csr),
Profile: req.Profile,
Label: req.Label,
}
certBytes, err := cg.signer.Sign(signReq)
if err != nil {
log.Warningf("failed to sign request: %v", err)
return err
}
reqSum, err := computeSum(csr)
if err != nil {
return errors.NewBadRequest(err)
}
certSum, err := computeSum(certBytes)
if err != nil {
return errors.NewBadRequest(err)
}
result := map[string]interface{}{
"private_key": string(key),
"certificate_request": string(csr),
"certificate": string(certBytes),
"sums": map[string]Sum{
"certificate_request": reqSum,
"certificate": certSum,
},
}
if req.Bundle {
if cg.bundler == nil {
return api.SendResponseWithMessage(w, result, NoBundlerMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
bundle, err := cg.bundler.BundleFromPEMorDER(certBytes, nil, bundler.Optimal, "")
if err != nil {
return err
}
result["bundle"] = bundle
}
if len(req.Request.Hosts) == 0 {
return api.SendResponseWithMessage(w, result, CSRNoHostMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
return api.SendResponse(w, result)
}
// CSRValidate does nothing and will never return an error. It exists because NewHandler
// requires a Validator as a parameter.
func CSRValidate(req *csr.CertificateRequest) error {
return nil
}

View File

@@ -0,0 +1,145 @@
package generator
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "testdata/ca.pem"
testCaKeyFile = "testdata/ca_key.pem"
testCABundle = "../../bundler/testdata/ca-bundle.pem"
testIntBundle = "../../bundler/testdata/int-bundle.pem"
)
func csrData(t *testing.T) *bytes.Reader {
req := &csr.CertificateRequest{
Names: []csr.Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com"},
KeyRequest: csr.NewBasicKeyRequest(),
}
csrBytes, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
return bytes.NewReader(csrBytes)
}
func TestGeneratorRESTfulVerbs(t *testing.T) {
handler, _ := NewHandler(CSRValidate)
ts := httptest.NewServer(handler)
data := csrData(t)
// POST should work.
req, _ := http.NewRequest("POST", ts.URL, data)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status)
}
// Test GET, PUT, DELETE and whatever, expect 400 errors.
req, _ = http.NewRequest("GET", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("PUT", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("DELETE", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("WHATEVER", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
}
func TestCSRValidate(t *testing.T) {
req := &csr.CertificateRequest{
Names: []csr.Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{},
KeyRequest: csr.NewBasicKeyRequest(),
}
err := CSRValidate(req)
if err != nil {
t.Fatal("There should be not an error for missing Hosts parameter")
}
}
func TestNewCertGeneratorHandlerFromSigner(t *testing.T) {
var expiry = 1 * time.Minute
var CAConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"signature": {
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "43800h",
Expiry: expiry,
CAConstraint: config.CAConstraint{IsCA: true},
ClientProvidesSerialNumbers: true,
},
},
}
s, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, CAConfig.Signing)
if err != nil {
t.Fatal(err)
}
h := NewCertGeneratorHandlerFromSigner(CSRValidate, s)
_, ok := h.(http.Handler)
if !ok {
t.Fatal("A HTTP handler has not been returned")
}
apiH, ok := h.(api.HTTPHandler)
if !ok {
t.Fatal("An api.HTTPHandler has not been returned")
}
cg, ok := apiH.Handler.(*CertGeneratorHandler)
if !ok {
t.Fatal("A CertGeneratorHandler has not been set")
}
if err := cg.SetBundler(testCABundle, testIntBundle); err != nil {
t.Fatal(err)
}
}

121
vendor/github.com/cloudflare/cfssl/api/info/info.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
// Package info implements the HTTP handler for the info command.
package info
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/info"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
)
// Handler is a type that contains the root certificates for the CA,
// and serves information on them for clients that need the certificates.
type Handler struct {
sign signer.Signer
}
// NewHandler creates a new handler to serve information on the CA's
// certificates, taking a signer to use.
func NewHandler(s signer.Signer) (http.Handler, error) {
return &api.HTTPHandler{
Handler: &Handler{
sign: s,
},
Methods: []string{"POST"},
}, nil
}
// Handle listens for incoming requests for CA information, and returns
// a list containing information on each root certificate.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
req := new(info.Req)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
resp, err := h.sign.Info(*req)
if err != nil {
return err
}
response := api.NewSuccessResponse(resp)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
return enc.Encode(response)
}
// MultiHandler is a handler for providing the public certificates for
// a multi-root certificate authority. It takes a mapping of label to
// signer and a default label, and handles the standard information
// request as defined in the client package.
type MultiHandler struct {
signers map[string]signer.Signer
defaultLabel string
}
// NewMultiHandler constructs a MultiHandler from a mapping of labels
// to signers and the default label.
func NewMultiHandler(signers map[string]signer.Signer, defaultLabel string) (http.Handler, error) {
return &api.HTTPHandler{
Handler: &MultiHandler{
signers: signers,
defaultLabel: defaultLabel,
},
Methods: []string{"POST"},
}, nil
}
// Handle accepts client information requests, and uses the label to
// look up the signer whose public certificate should be retrieved. If
// the label is empty, the default label is used.
func (h *MultiHandler) Handle(w http.ResponseWriter, r *http.Request) error {
req := new(info.Req)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
log.Debug("checking label")
if req.Label == "" {
req.Label = h.defaultLabel
}
if _, ok := h.signers[req.Label]; !ok {
log.Warningf("request for invalid endpoint")
return errors.NewBadRequestString("bad label")
}
log.Debug("getting info")
resp, err := h.signers[req.Label].Info(*req)
if err != nil {
log.Infof("error getting certificate: %v", err)
return err
}
response := api.NewSuccessResponse(resp)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
return enc.Encode(response)
}

View File

@@ -0,0 +1,256 @@
package info
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
// second test CA for multiroot
testCaFile2 = "../testdata/ca2.pem"
testCaKeyFile2 = "../testdata/ca2-key.pem"
)
// Generally, the single root function and its multiroot analogue will
// be presented together.
func newTestHandler(t *testing.T) (h http.Handler) {
signer, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
h, err = NewHandler(signer)
if err != nil {
t.Fatal(err)
}
return
}
func newTestMultiHandler(t *testing.T) (h http.Handler) {
signer1, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
signer2, err := local.NewSignerFromFile(testCaFile2, testCaKeyFile2, nil)
if err != nil {
t.Fatal(err)
}
signers := map[string]signer.Signer{
"test1": signer1,
"test2": signer2,
}
h, err = NewMultiHandler(signers, "test1")
if err != nil {
t.Fatalf("%v", err)
}
return
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func TestNewMultiHandler(t *testing.T) {
newTestMultiHandler(t)
}
func newInfoServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func newMultiInfoServer(t *testing.T) *httptest.Server {
return httptest.NewServer(newTestMultiHandler(t))
}
func testInfoFile(t *testing.T, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := newInfoServer(t)
defer ts.Close()
blob, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func testMultiInfoFile(t *testing.T, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := newMultiInfoServer(t)
defer ts.Close()
blob, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
type infoTest struct {
RequestObject map[string]interface{}
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
}
var infoTests = []infoTest{
{
map[string]interface{}{
"label": "",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": 123,
},
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
}
var multiInfoTests = []infoTest{
{
map[string]interface{}{
"label": "",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "test1",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "test2",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "badlabel",
"profile": "",
},
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
{
map[string]interface{}{
"label": 123,
},
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
}
func TestInfo(t *testing.T) {
for i, test := range infoTests {
resp, body := testInfoFile(t, test.RequestObject)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Fatalf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}
func TestMultiInfo(t *testing.T) {
for i, test := range multiInfoTests {
resp, body := testMultiInfoFile(t, test.RequestObject)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Fatalf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}

View File

@@ -0,0 +1,61 @@
// Package initca implements the HTTP handler for the CA initialization command
package initca
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/initca"
"github.com/cloudflare/cfssl/log"
)
// A NewCA contains a private key and certificate suitable for serving
// as the root key for a new certificate authority.
type NewCA struct {
Key string `json:"private_key"`
Cert string `json:"certificate"`
}
// initialCAHandler is an HTTP handler that accepts a JSON blob in the
// same format as the CSR endpoint; this blob should contain the
// identity information for the CA's root key. This endpoint is not
// suitable for creating intermediate certificates.
func initialCAHandler(w http.ResponseWriter, r *http.Request) error {
log.Info("setting up initial CA handler")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
req := new(csr.CertificateRequest)
req.KeyRequest = csr.NewBasicKeyRequest()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
cert, _, key, err := initca.New(req)
if err != nil {
log.Warningf("failed to initialise new CA: %v", err)
return err
}
response := api.NewSuccessResponse(&NewCA{string(key), string(cert)})
enc := json.NewEncoder(w)
err = enc.Encode(response)
return err
}
// NewHandler returns a new http.Handler that handles request to
// initialize a CA.
func NewHandler() http.Handler {
return api.HTTPHandler{Handler: api.HandlerFunc(initialCAHandler), Methods: []string{"POST"}}
}

View File

@@ -0,0 +1,90 @@
package initca
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudflare/cfssl/csr"
)
func csrData(t *testing.T) *bytes.Reader {
req := &csr.CertificateRequest{
Names: []csr.Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com"},
KeyRequest: csr.NewBasicKeyRequest(),
}
csrBytes, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
return bytes.NewReader(csrBytes)
}
func TestInitCARESTfulVerbs(t *testing.T) {
ts := httptest.NewServer(NewHandler())
data := csrData(t)
// POST should work.
req, _ := http.NewRequest("POST", ts.URL, data)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status)
}
// Test GET, PUT, DELETE and whatever, expect 400 errors.
req, _ = http.NewRequest("GET", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("PUT", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("DELETE", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("WHATEVER", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
}
func TestBadRequestBody(t *testing.T) {
ts := httptest.NewServer(NewHandler())
req, _ := http.NewRequest("POST", ts.URL, nil)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode == http.StatusOK {
t.Fatal(resp.Status)
}
}
func TestBadRequestBody_2(t *testing.T) {
ts := httptest.NewServer(NewHandler())
r := &csr.CertificateRequest{}
csrBytes, err := json.Marshal(r)
if err != nil {
t.Fatal(err)
}
data := bytes.NewReader(csrBytes)
req, _ := http.NewRequest("POST", ts.URL, data)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode == http.StatusOK {
t.Fatal(resp.Status)
}
}

113
vendor/github.com/cloudflare/cfssl/api/ocsp/ocspsign.go generated vendored Normal file
View File

@@ -0,0 +1,113 @@
// Package ocsp implements the HTTP handler for the ocsp commands.
package ocsp
import (
"crypto"
"net/http"
"encoding/base64"
"encoding/json"
"io/ioutil"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/ocsp"
)
// A Handler accepts requests with a certficate parameter
// (which should be PEM-encoded) and returns a signed ocsp
// response.
type Handler struct {
signer ocsp.Signer
}
// NewHandler returns a new http.Handler that handles a ocspsign request.
func NewHandler(s ocsp.Signer) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
signer: s,
},
Methods: []string{"POST"},
}
}
// This type is meant to be unmarshalled from JSON
type jsonSignRequest struct {
Certificate string `json:"certificate"`
Status string `json:"status"`
Reason int `json:"reason,omitempty"`
RevokedAt string `json:"revoked_at,omitempty"`
IssuerHash string `json:"issuer_hash,omitempty"`
}
var nameToHash = map[string]crypto.Hash{
"MD5": crypto.MD5,
"SHA1": crypto.SHA1,
"SHA256": crypto.SHA256,
"SHA384": crypto.SHA384,
"SHA512": crypto.SHA512,
}
// Handle responds to requests for a ocsp signature. It creates and signs
// a ocsp response for the provided certificate and status. If the status
// is revoked then it also adds reason and revoked_at. The response is
// base64 encoded.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
// Default the status to good so it matches the cli
req := &jsonSignRequest{
Status: "good",
}
err = json.Unmarshal(body, req)
if err != nil {
return errors.NewBadRequestString("Unable to parse sign request")
}
cert, err := helpers.ParseCertificatePEM([]byte(req.Certificate))
if err != nil {
log.Error("Error from ParseCertificatePEM", err)
return errors.NewBadRequestString("Malformed certificate")
}
signReq := ocsp.SignRequest{
Certificate: cert,
Status: req.Status,
}
// We need to convert the time from being a string to a time.Time
if req.Status == "revoked" {
signReq.Reason = req.Reason
// "now" is accepted and the default on the cli so default that here
if req.RevokedAt == "" || req.RevokedAt == "now" {
signReq.RevokedAt = time.Now()
} else {
signReq.RevokedAt, err = time.Parse("2006-01-02", req.RevokedAt)
if err != nil {
return errors.NewBadRequestString("Malformed revocation time")
}
}
}
if req.IssuerHash != "" {
issuerHash, ok := nameToHash[req.IssuerHash]
if !ok {
return errors.NewBadRequestString("Unsupported hash algorithm in request")
}
signReq.IssuerHash = issuerHash
}
resp, err := h.signer.Sign(signReq)
if err != nil {
return err
}
b64Resp := base64.StdEncoding.EncodeToString(resp)
result := map[string]string{"ocspResponse": b64Resp}
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,242 @@
package ocsp
import (
"bytes"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/ocsp"
goocsp "golang.org/x/crypto/ocsp"
"github.com/cloudflare/cfssl/helpers"
)
const (
testCaFile = "../../ocsp/testdata/ca.pem"
testRespCertFile = "../../ocsp/testdata/server.crt"
testKeyFile = "../../ocsp/testdata/server.key"
testCertFile = "../../ocsp/testdata/cert.pem"
)
func newTestHandler(t *testing.T) http.Handler {
// arbitrary duration
dur, _ := time.ParseDuration("1ms")
s, err := ocsp.NewSignerFromFile(testCaFile, testRespCertFile, testKeyFile, dur)
if err != nil {
t.Fatalf("Signer creation failed %v", err)
}
return NewHandler(s)
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func newSignServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testSignFile(t *testing.T, certFile, status string, reason int, revokedAt string, hash string) (resp *http.Response, body []byte) {
ts := newSignServer(t)
defer ts.Close()
obj := map[string]interface{}{}
if certFile != "" {
c, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
obj["certificate"] = string(c)
}
if status != "" {
obj["status"] = status
}
obj["reason"] = reason
if revokedAt != "" {
obj["revoked_at"] = revokedAt
}
obj["issuer_hash"] = hash
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
type signTest struct {
CertificateFile string
Status string
Reason int
RevokedAt string
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
IssuerHash string
}
var signTests = []signTest{
{
CertificateFile: testCertFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
Reason: 1,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
RevokedAt: "now",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
RevokedAt: "2015-08-15",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
RevokedAt: "a",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
CertificateFile: "",
Status: "",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
CertificateFile: testCertFile,
Status: "_",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 8200,
},
{
CertificateFile: testCertFile,
IssuerHash: "SHA256",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
IssuerHash: "MD4",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
}
func TestSign(t *testing.T) {
for i, test := range signTests {
resp, body := testSignFile(t, test.CertificateFile, test.Status, test.Reason, test.RevokedAt, test.IssuerHash)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Logf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if !test.ExpectedSuccess {
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
continue
}
result, ok := message.Result.(map[string]interface{})
if !ok {
t.Logf("failed to read result")
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
b64Resp, ok := result["ocspResponse"].(string)
if !ok {
t.Logf("failed to find ocspResponse")
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
der, err := base64.StdEncoding.DecodeString(b64Resp)
if err != nil {
t.Logf("failed to decode base64")
t.Fatal(resp.Status, test.ExpectedHTTPStatus, b64Resp)
}
ocspResp, err := goocsp.ParseResponse(der, nil)
if err != nil {
t.Logf("failed to parse ocsp response: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, b64Resp)
}
//should default to good
if test.Status == "" {
test.Status = "good"
}
intStatus := ocsp.StatusCode[test.Status]
if ocspResp.Status != intStatus {
t.Fatalf("Test %d incorrect status: expected: %v, have %v", i, intStatus, ocspResp.Status)
t.Fatal(ocspResp.Status, intStatus, ocspResp)
}
if test.Status == "revoked" {
if ocspResp.RevocationReason != test.Reason {
t.Fatalf("Test %d incorrect reason: expected: %v, have %v", i, test.Reason, ocspResp.RevocationReason)
t.Fatal(ocspResp.RevocationReason, test.Reason, ocspResp)
}
var r time.Time
if test.RevokedAt == "" || test.RevokedAt == "now" {
r = time.Now().UTC().Truncate(helpers.OneDay)
} else {
r, _ = time.Parse("2006-01-02", test.RevokedAt)
}
if !ocspResp.RevokedAt.Truncate(helpers.OneDay).Equal(r) {
t.Fatalf("Test %d incorrect revokedAt: expected: %v, have %v", i, r, ocspResp.RevokedAt)
t.Fatal(ocspResp.RevokedAt, test.RevokedAt, ocspResp)
}
}
}
}

137
vendor/github.com/cloudflare/cfssl/api/revoke/revoke.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
// Package revoke implements the HTTP handler for the revoke command
package revoke
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/ocsp"
stdocsp "golang.org/x/crypto/ocsp"
)
// A Handler accepts requests with a serial number parameter
// and revokes
type Handler struct {
dbAccessor certdb.Accessor
Signer ocsp.Signer
}
// NewHandler returns a new http.Handler that handles a revoke request.
func NewHandler(dbAccessor certdb.Accessor) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
},
Methods: []string{"POST"},
}
}
// NewOCSPHandler returns a new http.Handler that handles a revoke
// request and also generates an OCSP response
func NewOCSPHandler(dbAccessor certdb.Accessor, signer ocsp.Signer) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
Signer: signer,
},
Methods: []string{"POST"},
}
}
// This type is meant to be unmarshalled from JSON
type jsonRevokeRequest struct {
Serial string `json:"serial"`
AKI string `json:"authority_key_id"`
Reason string `json:"reason"`
}
// Handle responds to revocation requests. It attempts to revoke
// a certificate with a given serial number
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
// Default the status to good so it matches the cli
var req jsonRevokeRequest
err = json.Unmarshal(body, &req)
if err != nil {
return errors.NewBadRequestString("Unable to parse revocation request")
}
if len(req.Serial) == 0 {
return errors.NewBadRequestString("serial number is required but not provided")
}
var reasonCode int
reasonCode, err = ocsp.ReasonStringToCode(req.Reason)
if err != nil {
return errors.NewBadRequestString("Invalid reason code")
}
err = h.dbAccessor.RevokeCertificate(req.Serial, req.AKI, reasonCode)
if err != nil {
return err
}
// If we were given a signer, try and generate an OCSP
// response indicating revocation
if h.Signer != nil {
// TODO: should these errors be errors?
// Grab the certificate from the database
cr, err := h.dbAccessor.GetCertificate(req.Serial, req.AKI)
if err != nil {
return err
}
if len(cr) != 1 {
return errors.NewBadRequestString("No unique certificate found")
}
cert, err := helpers.ParseCertificatePEM([]byte(cr[0].PEM))
if err != nil {
return errors.NewBadRequestString("Unable to parse certificates from PEM data")
}
sr := ocsp.SignRequest{
Certificate: cert,
Status: "revoked",
Reason: reasonCode,
RevokedAt: time.Now().UTC(),
}
ocspResponse, err := h.Signer.Sign(sr)
if err != nil {
return err
}
// We parse the OCSP repsonse in order to get the next
// update time/expiry time
ocspParsed, err := stdocsp.ParseResponse(ocspResponse, nil)
if err != nil {
return err
}
ocspRecord := certdb.OCSPRecord{
Serial: req.Serial,
AKI: req.AKI,
Body: string(ocspResponse),
Expiry: ocspParsed.NextUpdate,
}
if err = h.dbAccessor.InsertOCSP(ocspRecord); err != nil {
return err
}
}
result := map[string]string{}
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,295 @@
package revoke
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/json"
"encoding/pem"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/ocsp"
stdocsp "golang.org/x/crypto/ocsp"
)
const (
fakeAKI = "fake aki"
)
func prepDB() (certdb.Accessor, error) {
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
expirationTime := time.Now().AddDate(1, 0, 0)
var cert = certdb.CertificateRecord{
Serial: "1",
AKI: fakeAKI,
Expiry: expirationTime,
PEM: "unexpired cert",
}
dbAccessor := sql.NewAccessor(db)
err := dbAccessor.InsertCertificate(cert)
if err != nil {
return nil, err
}
return dbAccessor, nil
}
func testRevokeCert(t *testing.T, dbAccessor certdb.Accessor, serial, aki, reason string) (resp *http.Response, body []byte) {
ts := httptest.NewServer(NewHandler(dbAccessor))
defer ts.Close()
obj := map[string]interface{}{}
obj["serial"] = serial
obj["authority_key_id"] = aki
if reason != "" {
obj["reason"] = reason
}
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func TestInvalidRevocation(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, _ := testRevokeCert(t, dbAccessor, "", "", "")
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("expected bad request response")
}
}
func TestRevocation(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, body := testRevokeCert(t, dbAccessor, "1", fakeAKI, "5")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
certs, err := dbAccessor.GetCertificate("1", fakeAKI)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.Status != "revoked" || cert.Reason != 5 {
t.Fatal("cert was not correctly revoked")
}
}
// TestOCSPGeneration tests that revoking a certificate (when the
// request handler has an OCSP response signer) generates an
// appropriate OCSP response in the certdb.
func TestOCSPGeneration(t *testing.T) {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128)
// 1. Generate a CA certificate to serve as the signing certificate.
issuerSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
t.Fatal(err)
}
issuerTemplate := x509.Certificate{
SerialNumber: issuerSerial,
Subject: pkix.Name{
Organization: []string{"cfssl unit test"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
IsCA: true,
BasicConstraintsValid: true,
}
issuerBytes, err := x509.CreateCertificate(rand.Reader, &issuerTemplate, &issuerTemplate, &privKey.PublicKey, privKey)
if err != nil {
t.Fatal(err)
}
issuer, err := x509.ParseCertificate(issuerBytes)
if err != nil {
t.Fatal(err)
}
// 2. Generate a certificate signed by the CA certificate to revoke.
revokedSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
t.Fatal(err)
}
revokedTemplate := x509.Certificate{
SerialNumber: revokedSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
}
revokedBytes, err := x509.CreateCertificate(rand.Reader, &revokedTemplate, issuer, &privKey.PublicKey, privKey)
if err != nil {
t.Fatal(err)
}
revoked := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: revokedBytes,
})
revokedAKI := hex.EncodeToString(revokedTemplate.AuthorityKeyId)
revokedSerialStr := revokedSerial.Text(16)
// 3. Generate a certificate to use as the responder certificate.
responderSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
t.Fatal(err)
}
responderTemplate := x509.Certificate{
SerialNumber: responderSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152 Responder"},
},
AuthorityKeyId: []byte{42, 42, 42, 43},
}
responderBytes, err := x509.CreateCertificate(rand.Reader, &responderTemplate, &responderTemplate, &privKey.PublicKey, privKey)
if err != nil {
t.Fatal(err)
}
responder, err := x509.ParseCertificate(responderBytes)
if err != nil {
t.Fatal(err)
}
// 4. Create the OCSP signer
signer, err := ocsp.NewSigner(issuer, responder, privKey, time.Hour)
if err != nil {
t.Fatal(err)
}
// 5. Spin up the test server
// 5a. Prepare the DB
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
expirationTime := time.Now().AddDate(1, 0, 0)
cr := certdb.CertificateRecord{
Serial: revokedSerialStr,
AKI: revokedAKI,
Expiry: expirationTime,
PEM: string(revoked),
}
if err := dbAccessor.InsertCertificate(cr); err != nil {
t.Fatal(err)
}
// 5b. Start the test server
ts := httptest.NewServer(NewOCSPHandler(dbAccessor, signer))
defer ts.Close()
// 6. Prepare the revocation request
obj := map[string]interface{}{}
obj["serial"] = revokedSerialStr
obj["authority_key_id"] = revokedAKI
obj["reason"] = "unspecified"
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
// Get the original number of OCSP responses
ocspsBefore, _ := dbAccessor.GetOCSP(revokedSerialStr, revokedAKI)
ocspCountBefore := len(ocspsBefore)
// 7. Send the revocation request
resp, err := http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
// 8. Make sure the certificate record was updated
certs, err := dbAccessor.GetCertificate(revokedSerialStr, revokedAKI)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.Status != "revoked" || cert.Reason != stdocsp.Unspecified {
t.Fatal("cert was not correctly revoked")
}
// 9. Make sure there is an OCSP record
ocsps, err := dbAccessor.GetOCSP(revokedSerialStr, revokedAKI)
if err != nil {
t.Fatal("failed to get OCSP responses ", err)
}
if len(ocsps) == 0 {
t.Fatal("No OCSP response generated")
}
if len(ocsps) <= ocspCountBefore {
t.Fatal("No new OCSP response found")
}
}

76
vendor/github.com/cloudflare/cfssl/api/scan/scan.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
package scan
import (
"encoding/json"
"net/http"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/scan"
)
// scanHandler is an HTTP handler that accepts GET parameters for host (required)
// family and scanner, and uses these to perform scans, returning a JSON blob result.
func scanHandler(w http.ResponseWriter, r *http.Request) error {
if err := r.ParseForm(); err != nil {
log.Warningf("failed to parse body: %v", err)
return errors.NewBadRequest(err)
}
family := r.Form.Get("family")
scanner := r.Form.Get("scanner")
ip := r.Form.Get("ip")
timeoutStr := r.Form.Get("timeout")
var timeout time.Duration
var err error
if timeoutStr != "" {
if timeout, err = time.ParseDuration(timeoutStr); err != nil {
return errors.NewBadRequest(err)
}
if timeout < time.Second || timeout > 5*time.Minute {
return errors.NewBadRequestString("invalid timeout given")
}
} else {
timeout = time.Minute
}
host := r.Form.Get("host")
if host == "" {
log.Warningf("no host given")
return errors.NewBadRequestString("no host given")
}
results, err := scan.Default.RunScans(host, ip, family, scanner, timeout)
if err != nil {
return errors.NewBadRequest(err)
}
return json.NewEncoder(w).Encode(api.NewSuccessResponse(results))
}
// NewHandler returns a new http.Handler that handles a scan request.
func NewHandler(caBundleFile string) (http.Handler, error) {
return api.HTTPHandler{
Handler: api.HandlerFunc(scanHandler),
Methods: []string{"GET"},
}, scan.LoadRootCAs(caBundleFile)
}
// scanInfoHandler is an HTTP handler that returns a JSON blob result describing
// the possible families and scans to be run.
func scanInfoHandler(w http.ResponseWriter, r *http.Request) error {
log.Info("setting up scaninfo handler")
response := api.NewSuccessResponse(scan.Default)
enc := json.NewEncoder(w)
return enc.Encode(response)
}
// NewInfoHandler returns a new http.Handler that handles a request for scan info.
func NewInfoHandler() http.Handler {
return api.HTTPHandler{
Handler: api.HandlerFunc(scanInfoHandler),
Methods: []string{"GET"},
}
}

View File

@@ -0,0 +1,63 @@
package scan
import (
"net/http"
"net/http/httptest"
"testing"
)
var (
handler, _ = NewHandler("")
ts = httptest.NewServer(handler)
)
func TestBadRequest(t *testing.T) {
// Test request with no host
req, _ := http.NewRequest("GET", ts.URL, nil)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusBadRequest {
t.Fatal(resp.Status)
}
}
func TestScanRESTfulVerbs(t *testing.T) {
// GET should work
req, _ := http.NewRequest("GET", ts.URL, nil)
data := req.URL.Query()
data.Add("host", "cloudflare.com")
req.URL.RawQuery = data.Encode()
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status)
}
// POST, PUT, DELETE, WHATEVER should return 400 errors
req, _ = http.NewRequest("POST", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("DELETE", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("PUT", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("WHATEVER", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
}
func TestNewInfoHandler(t *testing.T) {
handler := NewInfoHandler()
if handler == nil {
t.Fatal("Handler error")
}
}

51
vendor/github.com/cloudflare/cfssl/api/sign/sign.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
// Package sign implements the HTTP handler for the certificate signing command.
package sign
import (
"net/http"
"github.com/cloudflare/cfssl/api/signhandler"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer/universal"
)
// NewHandler generates a new Handler using the certificate
// authority private key and certficate to sign certificates. If remote
// is not an empty string, the handler will send signature requests to
// the CFSSL instance contained in remote by default.
func NewHandler(caFile, caKeyFile string, policy *config.Signing) (http.Handler, error) {
root := universal.Root{
Config: map[string]string{
"cert-file": caFile,
"key-file": caKeyFile,
},
}
s, err := universal.NewSigner(root, policy)
if err != nil {
log.Errorf("setting up signer failed: %v", err)
return nil, err
}
return signhandler.NewHandlerFromSigner(s)
}
// NewAuthHandler generates a new AuthHandler using the certificate
// authority private key and certficate to sign certificates. If remote
// is not an empty string, the handler will send signature requests to
// the CFSSL instance contained in remote by default.
func NewAuthHandler(caFile, caKeyFile string, policy *config.Signing) (http.Handler, error) {
root := universal.Root{
Config: map[string]string{
"cert-file": caFile,
"key-file": caKeyFile,
},
}
s, err := universal.NewSigner(root, policy)
if err != nil {
log.Errorf("setting up signer failed: %v", err)
return nil, err
}
return signhandler.NewAuthHandlerFromSigner(s)
}

View File

@@ -0,0 +1,531 @@
package sign
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/signer"
)
const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
testCSRFile = "../testdata/csr.pem"
testBrokenCertFile = "../testdata/broken.pem"
testBrokenCSRFile = "../testdata/broken_csr.pem"
)
var validLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m"
}
}
}`
var validAuthLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m",
"auth_key": "sample"
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
}
}`
var validMixedLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m"
},
"profiles": {
"auth": {
"usages": ["digital signature", "email protection"],
"expiry": "1m",
"auth_key": "sample"
}
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
}
}`
var alsoValidMixedLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m",
"auth_key": "sample"
},
"profiles": {
"no-auth": {
"usages": ["digital signature", "email protection"],
"expiry": "1m"
}
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
}
}`
func newTestHandler(t *testing.T) (h http.Handler) {
h, err := NewHandler(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func TestNewHandlerWithProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(validLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal(err)
}
}
func TestNewHandlerWithAuthProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err == nil {
t.Fatal("All profiles have auth keys. Should have failed to create non-auth sign handler.")
}
}
func TestNewHandlerError(t *testing.T) {
// using testBrokenCSRFile as badly formed key
_, err := NewHandler(testCaFile, testBrokenCSRFile, nil)
if err == nil {
t.Fatal("Expect error when create a signer with broken file.")
}
}
func TestNewAuthHandlerWithNonAuthProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(validLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err == nil {
t.Fatal("No profile have auth keys. Should have failed to create auth sign handler.")
}
}
func TestNewHandlersWithMixedProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(validMixedLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create non-auth sign handler.")
}
_, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create auth sign handler.")
}
}
func TestNewHandlersWithAnotherMixedProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(alsoValidMixedLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create non-auth sign handler.")
}
_, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create auth sign handler.")
}
}
func newSignServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testSignFileOldInterface(t *testing.T, hostname, csrFile string) (resp *http.Response, body []byte) {
ts := newSignServer(t)
defer ts.Close()
var csrPEM []byte
if csrFile != "" {
var err error
csrPEM, err = ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]string{}
if len(hostname) > 0 {
obj["hostname"] = hostname
}
if len(csrPEM) > 0 {
obj["certificate_request"] = string(csrPEM)
}
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func testSignFile(t *testing.T, hosts []string, subject *signer.Subject, csrFile string) (resp *http.Response, body []byte) {
ts := newSignServer(t)
defer ts.Close()
var csrPEM []byte
if csrFile != "" {
var err error
csrPEM, err = ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]interface{}{}
if hosts != nil {
obj["hosts"] = hosts
}
if len(csrPEM) > 0 {
obj["certificate_request"] = string(csrPEM)
}
if subject != nil {
obj["subject"] = subject
}
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
const (
testHostName = "localhost"
testDomainName = "cloudflare.com"
)
type signTest struct {
Hosts []string
Subject *signer.Subject
CSRFile string
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
}
var signTests = []signTest{
{
Hosts: []string{testHostName},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName, testHostName},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName},
Subject: &signer.Subject{CN: "example.com"},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{},
Subject: &signer.Subject{CN: "example.com"},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: nil,
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName},
CSRFile: "",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
Hosts: []string{testDomainName},
CSRFile: testBrokenCSRFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 9002,
},
}
func TestSign(t *testing.T) {
for i, test := range signTests {
resp, body := testSignFile(t, test.Hosts, test.Subject, test.CSRFile)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Logf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
// Test for backward compatibility
// TODO remove after API transition is complete.
for i, test := range signTests {
// an empty hostname is not accepted by the old interface but an empty hosts array should be accepted
// so skip the case of empty hosts array for the old interface.
if test.Hosts != nil && len(test.Hosts) == 0 {
continue
}
hostname := strings.Join(test.Hosts, ",")
resp, body := testSignFileOldInterface(t, hostname, test.CSRFile)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Logf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}
func newTestAuthHandler(t *testing.T) http.Handler {
conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
if err != nil {
t.Fatal(err)
}
h, err := NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal(err)
}
return h
}
func TestNewAuthHandler(t *testing.T) {
newTestAuthHandler(t)
}
func TestNewAuthHandlerWithNoAuthConfig(t *testing.T) {
conf, err := config.LoadConfig([]byte(validLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err == nil {
t.Fatal("Config doesn't have auth keys. Should have failed.")
}
return
}
func testAuthSignFile(t *testing.T, hosts []string, subject *signer.Subject, csrFile string, profile *config.SigningProfile) (resp *http.Response, body []byte) {
ts := newAuthSignServer(t)
defer ts.Close()
var csrPEM []byte
if csrFile != "" {
var err error
csrPEM, err = ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]interface{}{}
if hosts != nil {
obj["hosts"] = hosts
}
if subject != nil {
obj["subject"] = subject
}
if len(csrPEM) > 0 {
obj["certificate_request"] = string(csrPEM)
}
reqBlob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
var aReq auth.AuthenticatedRequest
aReq.Request = reqBlob
aReq.Token, err = profile.Provider.Token(aReq.Request)
if err != nil {
t.Fatal(err)
}
blob, err := json.Marshal(aReq)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func newAuthSignServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestAuthHandler(t))
return ts
}
func TestAuthSign(t *testing.T) {
conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
if err != nil {
t.Fatal(err)
}
for i, test := range signTests {
resp, body := testAuthSignFile(t, test.Hosts, test.Subject, test.CSRFile, conf.Signing.Default)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}

View File

@@ -0,0 +1,294 @@
// Package signhandler provides the handlers for signers.
package signhandler
import (
"encoding/json"
"io/ioutil"
"math/big"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/bundler"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
)
// NoBundlerMessage is used to alert the user that the server does not have a bundler initialized.
const NoBundlerMessage = `This request requires a bundler, but one is not initialized for the API server.`
// A Handler accepts requests with a hostname and certficate
// parameter (which should be PEM-encoded) and returns a new signed
// certificate. It includes upstream servers indexed by their
// profile name.
type Handler struct {
signer signer.Signer
bundler *bundler.Bundler
}
// NewHandlerFromSigner generates a new Handler directly from
// an existing signer.
func NewHandlerFromSigner(signer signer.Signer) (h *api.HTTPHandler, err error) {
policy := signer.Policy()
if policy == nil {
err = errors.New(errors.PolicyError, errors.InvalidPolicy)
return
}
// Sign will only respond for profiles that have no auth provider.
// So if all of the profiles require authentication, we return an error.
haveUnauth := (policy.Default.Provider == nil)
for _, profile := range policy.Profiles {
haveUnauth = haveUnauth || (profile.Provider == nil)
}
if !haveUnauth {
err = errors.New(errors.PolicyError, errors.InvalidPolicy)
return
}
return &api.HTTPHandler{
Handler: &Handler{
signer: signer,
},
Methods: []string{"POST"},
}, nil
}
// SetBundler allows injecting an optional Bundler into the Handler.
func (h *Handler) SetBundler(caBundleFile, intBundleFile string) (err error) {
h.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile)
return err
}
// This type is meant to be unmarshalled from JSON so that there can be a
// hostname field in the API
// TODO: Change the API such that the normal struct can be used.
type jsonSignRequest struct {
Hostname string `json:"hostname"`
Hosts []string `json:"hosts"`
Request string `json:"certificate_request"`
Subject *signer.Subject `json:"subject,omitempty"`
Profile string `json:"profile"`
Label string `json:"label"`
Serial *big.Int `json:"serial,omitempty"`
Bundle bool `json:"bundle"`
}
func jsonReqToTrue(js jsonSignRequest) signer.SignRequest {
sub := new(signer.Subject)
if js.Subject == nil {
sub = nil
} else {
// make a copy
*sub = *js.Subject
}
if js.Hostname != "" {
return signer.SignRequest{
Hosts: signer.SplitHosts(js.Hostname),
Subject: sub,
Request: js.Request,
Profile: js.Profile,
Label: js.Label,
Serial: js.Serial,
}
}
return signer.SignRequest{
Hosts: js.Hosts,
Subject: sub,
Request: js.Request,
Profile: js.Profile,
Label: js.Label,
Serial: js.Serial,
}
}
// Handle responds to requests for the CA to sign the certificate request
// present in the "certificate_request" parameter for the host named
// in the "hostname" parameter. The certificate should be PEM-encoded. If
// provided, subject information from the "subject" parameter will be used
// in place of the subject information from the CSR.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("signature request received")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
var req jsonSignRequest
err = json.Unmarshal(body, &req)
if err != nil {
return errors.NewBadRequestString("Unable to parse sign request")
}
signReq := jsonReqToTrue(req)
if req.Request == "" {
return errors.NewBadRequestString("missing parameter 'certificate_request'")
}
var cert []byte
profile, err := signer.Profile(h.signer, req.Profile)
if err != nil {
return err
}
if profile.Provider != nil {
log.Error("profile requires authentication")
return errors.NewBadRequestString("authentication required")
}
cert, err = h.signer.Sign(signReq)
if err != nil {
log.Warningf("failed to sign request: %v", err)
return err
}
result := map[string]interface{}{"certificate": string(cert)}
if req.Bundle {
if h.bundler == nil {
return api.SendResponseWithMessage(w, result, NoBundlerMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
bundle, err := h.bundler.BundleFromPEMorDER(cert, nil, bundler.Optimal, "")
if err != nil {
return err
}
result["bundle"] = bundle
}
log.Info("wrote response")
return api.SendResponse(w, result)
}
// An AuthHandler verifies and signs incoming signature requests.
type AuthHandler struct {
signer signer.Signer
bundler *bundler.Bundler
}
// NewAuthHandlerFromSigner creates a new AuthHandler from the signer
// that is passed in.
func NewAuthHandlerFromSigner(signer signer.Signer) (http.Handler, error) {
policy := signer.Policy()
if policy == nil {
return nil, errors.New(errors.PolicyError, errors.InvalidPolicy)
}
if policy.Default == nil && policy.Profiles == nil {
return nil, errors.New(errors.PolicyError, errors.InvalidPolicy)
}
// AuthSign will not respond for profiles that have no auth provider.
// So if there are no profiles with auth providers in this policy,
// we return an error.
haveAuth := (policy.Default.Provider != nil)
for _, profile := range policy.Profiles {
if haveAuth {
break
}
haveAuth = (profile.Provider != nil)
}
if !haveAuth {
return nil, errors.New(errors.PolicyError, errors.InvalidPolicy)
}
return &api.HTTPHandler{
Handler: &AuthHandler{
signer: signer,
},
Methods: []string{"POST"},
}, nil
}
// SetBundler allows injecting an optional Bundler into the Handler.
func (h *AuthHandler) SetBundler(caBundleFile, intBundleFile string) (err error) {
h.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile)
return err
}
// Handle receives the incoming request, validates it, and processes it.
func (h *AuthHandler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("signature request received")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Errorf("failed to read response body: %v", err)
return err
}
r.Body.Close()
var aReq auth.AuthenticatedRequest
err = json.Unmarshal(body, &aReq)
if err != nil {
log.Errorf("failed to unmarshal authenticated request: %v", err)
return errors.NewBadRequest(err)
}
var req jsonSignRequest
err = json.Unmarshal(aReq.Request, &req)
if err != nil {
log.Errorf("failed to unmarshal request from authenticated request: %v", err)
return errors.NewBadRequestString("Unable to parse authenticated sign request")
}
// Sanity checks to ensure that we have a valid policy. This
// should have been checked in NewAuthHandler.
policy := h.signer.Policy()
if policy == nil {
log.Critical("signer was initialised without a signing policy")
return errors.NewBadRequestString("invalid policy")
}
profile, err := signer.Profile(h.signer, req.Profile)
if err != nil {
return err
}
if profile.Provider == nil {
log.Error("profile has no authentication provider")
return errors.NewBadRequestString("no authentication provider")
}
if !profile.Provider.Verify(&aReq) {
log.Warning("received authenticated request with invalid token")
return errors.NewBadRequestString("invalid token")
}
signReq := jsonReqToTrue(req)
if signReq.Request == "" {
return errors.NewBadRequestString("missing parameter 'certificate_request'")
}
cert, err := h.signer.Sign(signReq)
if err != nil {
log.Errorf("signature failed: %v", err)
return err
}
result := map[string]interface{}{"certificate": string(cert)}
if req.Bundle {
if h.bundler == nil {
return api.SendResponseWithMessage(w, result, NoBundlerMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
bundle, err := h.bundler.BundleFromPEMorDER(cert, nil, bundler.Optimal, "")
if err != nil {
return err
}
result["bundle"] = bundle
}
log.Info("wrote response")
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,113 @@
package signhandler
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
testCSRFile = "../testdata/csr.pem"
)
// GetUnexpiredCertificates sometimes doesn't return a certificate with an
// expiry of 1m as above
var validLocalConfigLongerExpiry = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "10m"
}
}
}`
var dbAccessor certdb.Accessor
func TestSignerDBPersistence(t *testing.T) {
conf, err := config.LoadConfig([]byte(validLocalConfigLongerExpiry))
if err != nil {
t.Fatal(err)
}
var s *local.Signer
s, err = local.NewSignerFromFile(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal(err)
}
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
if err != nil {
t.Fatal(err)
}
dbAccessor = sql.NewAccessor(db)
s.SetDBAccessor(dbAccessor)
var handler *api.HTTPHandler
handler, err = NewHandlerFromSigner(signer.Signer(s))
if err != nil {
t.Fatal(err)
}
ts := httptest.NewServer(handler)
defer ts.Close()
var csrPEM, body []byte
csrPEM, err = ioutil.ReadFile(testCSRFile)
if err != nil {
t.Fatal(err)
}
blob, err := json.Marshal(&map[string]string{"certificate_request": string(csrPEM)})
if err != nil {
t.Fatal(err)
}
var resp *http.Response
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status, string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
if !message.Success {
t.Fatal("API operation failed")
}
crs, err := dbAccessor.GetUnexpiredCertificates()
if err != nil {
t.Fatal("Failed to get unexpired certificates")
}
if len(crs) != 1 {
t.Fatal("Expected 1 unexpired certificate in the database after signing 1: len(crs)=", len(crs))
}
}

Some files were not shown because too many files have changed in this diff Show More