mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Big dependency update, all lowercase sirupsen's for all dependencies.
This commit is contained in:
@@ -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
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
vendor/
|
||||||
|
_vendor*/
|
||||||
@@ -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/
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package stats
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogReporter struct {
|
type LogReporter struct {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NewRelicAgentConfig struct {
|
type NewRelicAgentConfig struct {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPSubHandler interface {
|
type HTTPSubHandler interface {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
2
cli/glide.lock
generated
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -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
186
glide.lock
generated
@@ -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
|
||||||
|
|||||||
21
glide.yaml
21
glide.yaml
@@ -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:
|
||||||
|
|||||||
59
vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
59
vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
@@ -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!")
|
|
||||||
}
|
|
||||||
30
vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
30
vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
@@ -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!")
|
|
||||||
}
|
|
||||||
10
vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
10
vendor/github.com/Sirupsen/logrus/terminal_appengine.go
generated
vendored
@@ -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
|
|
||||||
}
|
|
||||||
28
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
28
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
21
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
33
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
33
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
@@ -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
2
vendor/github.com/beorn7/perks/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.test
|
||||||
|
*.prof
|
||||||
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal 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
31
vendor/github.com/beorn7/perks/README.md
generated
vendored
Normal 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 Labs–Research), S. Muthukrishnan (Rutgers University), and
|
||||||
|
Divesh Srivastava (AT&T Labs–Research) 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
26
vendor/github.com/beorn7/perks/histogram/bench_test.go
generated
vendored
Normal 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
108
vendor/github.com/beorn7/perks/histogram/histogram.go
generated
vendored
Normal 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()
|
||||||
|
}
|
||||||
38
vendor/github.com/beorn7/perks/histogram/histogram_test.go
generated
vendored
Normal file
38
vendor/github.com/beorn7/perks/histogram/histogram_test.go
generated
vendored
Normal 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
63
vendor/github.com/beorn7/perks/quantile/bench_test.go
generated
vendored
Normal 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
121
vendor/github.com/beorn7/perks/quantile/example_test.go
generated
vendored
Normal 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
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
292
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal 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
215
vendor/github.com/beorn7/perks/quantile/stream_test.go
generated
vendored
Normal 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
90
vendor/github.com/beorn7/perks/topk/topk.go
generated
vendored
Normal 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
57
vendor/github.com/beorn7/perks/topk/topk_test.go
generated
vendored
Normal 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
4
vendor/github.com/cloudflare/cfssl/.dockerignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
cfssl_*
|
||||||
|
*-amd64
|
||||||
|
*-386
|
||||||
|
dist/*
|
||||||
4
vendor/github.com/cloudflare/cfssl/.gitignore
generated
vendored
Normal file
4
vendor/github.com/cloudflare/cfssl/.gitignore
generated
vendored
Normal 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
77
vendor/github.com/cloudflare/cfssl/.travis.yml
generated
vendored
Normal 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
38
vendor/github.com/cloudflare/cfssl/BUILDING.md
generated
vendored
Normal 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
72
vendor/github.com/cloudflare/cfssl/CHANGELOG
generated
vendored
Normal 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
16
vendor/github.com/cloudflare/cfssl/Dockerfile
generated
vendored
Normal 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
11
vendor/github.com/cloudflare/cfssl/Dockerfile.build
generated
vendored
Normal 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
39
vendor/github.com/cloudflare/cfssl/Dockerfile.minimal
generated
vendored
Normal 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
24
vendor/github.com/cloudflare/cfssl/LICENSE
generated
vendored
Normal 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
447
vendor/github.com/cloudflare/cfssl/README.md
generated
vendored
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
# CFSSL
|
||||||
|
|
||||||
|
[](https://travis-ci.org/cloudflare/cfssl)
|
||||||
|
[](http://codecov.io/github/cloudflare/cfssl?branch=master)
|
||||||
|
[](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 you’re wishing to distribute a single,
|
||||||
|
statically-linked, cfssl binary, you’ll 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
231
vendor/github.com/cloudflare/cfssl/api/api.go
generated
vendored
Normal 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
220
vendor/github.com/cloudflare/cfssl/api/api_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
91
vendor/github.com/cloudflare/cfssl/api/bundle/bundle.go
generated
vendored
Normal file
91
vendor/github.com/cloudflare/cfssl/api/bundle/bundle.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
213
vendor/github.com/cloudflare/cfssl/api/bundle/bundle_test.go
generated
vendored
Normal file
213
vendor/github.com/cloudflare/cfssl/api/bundle/bundle_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
193
vendor/github.com/cloudflare/cfssl/api/certadd/insert.go
generated
vendored
Normal file
193
vendor/github.com/cloudflare/cfssl/api/certadd/insert.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
491
vendor/github.com/cloudflare/cfssl/api/certadd/insert_test.go
generated
vendored
Normal file
491
vendor/github.com/cloudflare/cfssl/api/certadd/insert_test.go
generated
vendored
Normal 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
50
vendor/github.com/cloudflare/cfssl/api/certinfo/certinfo.go
generated
vendored
Normal file
50
vendor/github.com/cloudflare/cfssl/api/certinfo/certinfo.go
generated
vendored
Normal 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
6
vendor/github.com/cloudflare/cfssl/api/client/api.go
generated
vendored
Normal 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
346
vendor/github.com/cloudflare/cfssl/api/client/client.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
203
vendor/github.com/cloudflare/cfssl/api/client/client_test.go
generated
vendored
Normal file
203
vendor/github.com/cloudflare/cfssl/api/client/client_test.go
generated
vendored
Normal 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
125
vendor/github.com/cloudflare/cfssl/api/client/group.go
generated
vendored
Normal 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
93
vendor/github.com/cloudflare/cfssl/api/crl/crl.go
generated
vendored
Normal 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
149
vendor/github.com/cloudflare/cfssl/api/crl/crl_test.go
generated
vendored
Normal 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
102
vendor/github.com/cloudflare/cfssl/api/gencrl/gencrl.go
generated
vendored
Normal 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"},
|
||||||
|
}
|
||||||
|
}
|
||||||
107
vendor/github.com/cloudflare/cfssl/api/gencrl/gencrl_test.go
generated
vendored
Normal file
107
vendor/github.com/cloudflare/cfssl/api/gencrl/gencrl_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
319
vendor/github.com/cloudflare/cfssl/api/generator/generator.go
generated
vendored
Normal file
319
vendor/github.com/cloudflare/cfssl/api/generator/generator.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
145
vendor/github.com/cloudflare/cfssl/api/generator/generator_test.go
generated
vendored
Normal file
145
vendor/github.com/cloudflare/cfssl/api/generator/generator_test.go
generated
vendored
Normal 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
121
vendor/github.com/cloudflare/cfssl/api/info/info.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
256
vendor/github.com/cloudflare/cfssl/api/info/info_test.go
generated
vendored
Normal file
256
vendor/github.com/cloudflare/cfssl/api/info/info_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
61
vendor/github.com/cloudflare/cfssl/api/initca/initca.go
generated
vendored
Normal file
61
vendor/github.com/cloudflare/cfssl/api/initca/initca.go
generated
vendored
Normal 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"}}
|
||||||
|
}
|
||||||
90
vendor/github.com/cloudflare/cfssl/api/initca/initca_test.go
generated
vendored
Normal file
90
vendor/github.com/cloudflare/cfssl/api/initca/initca_test.go
generated
vendored
Normal 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
113
vendor/github.com/cloudflare/cfssl/api/ocsp/ocspsign.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
242
vendor/github.com/cloudflare/cfssl/api/ocsp/ocspsign_test.go
generated
vendored
Normal file
242
vendor/github.com/cloudflare/cfssl/api/ocsp/ocspsign_test.go
generated
vendored
Normal 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
137
vendor/github.com/cloudflare/cfssl/api/revoke/revoke.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
295
vendor/github.com/cloudflare/cfssl/api/revoke/revoke_test.go
generated
vendored
Normal file
295
vendor/github.com/cloudflare/cfssl/api/revoke/revoke_test.go
generated
vendored
Normal 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
76
vendor/github.com/cloudflare/cfssl/api/scan/scan.go
generated
vendored
Normal 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"},
|
||||||
|
}
|
||||||
|
}
|
||||||
63
vendor/github.com/cloudflare/cfssl/api/scan/scan_test.go
generated
vendored
Normal file
63
vendor/github.com/cloudflare/cfssl/api/scan/scan_test.go
generated
vendored
Normal 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
51
vendor/github.com/cloudflare/cfssl/api/sign/sign.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
531
vendor/github.com/cloudflare/cfssl/api/sign/sign_test.go
generated
vendored
Normal file
531
vendor/github.com/cloudflare/cfssl/api/sign/sign_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
294
vendor/github.com/cloudflare/cfssl/api/signhandler/signhandler.go
generated
vendored
Normal file
294
vendor/github.com/cloudflare/cfssl/api/signhandler/signhandler.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
113
vendor/github.com/cloudflare/cfssl/api/signhandler/signhandler_test.go
generated
vendored
Normal file
113
vendor/github.com/cloudflare/cfssl/api/signhandler/signhandler_test.go
generated
vendored
Normal 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
Reference in New Issue
Block a user