Enable authentication/authorization for featurebase tools

- Add auth-token for featurebase import, backup and restore
- Add auth-token to http request
- Create a cluster tests with auth enabled
- Add test for import with auth enabled
This commit is contained in:
Souhaila Noor 2022-01-26 17:30:26 -06:00
parent d7c3fa2a93
commit 0e1cf5bbbd
36 changed files with 1294 additions and 50 deletions

View file

@ -268,6 +268,7 @@ clustertests:
- if: '$CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"'
script:
- make clustertests
- make authclustertests
external lookup tests:
stage: integration

View file

@ -158,6 +158,14 @@ clustertests: vendor
PROJECT=$(PROJECT) $(DOCKER_COMPOSE) -f internal/clustertests/docker-compose.yml run client1
$(DOCKER_COMPOSE) -f internal/clustertests/docker-compose.yml down
# Run the cluster tests with authentication enabled
DOCKER_COMPOSE_AUTH = docker-compose -p authclustertests
authclustertests: vendor
$(DOCKER_COMPOSE_AUTH) -f internal/authclustertests/docker-compose.yml down
$(DOCKER_COMPOSE_AUTH) -f internal/authclustertests/docker-compose.yml build
$(DOCKER_COMPOSE_AUTH) -f internal/authclustertests/docker-compose.yml up -d pilosa1 pilosa2 pilosa3
$(DOCKER_COMPOSE_AUTH) -f internal/authclustertests/docker-compose.yml run client1
$(DOCKER_COMPOSE_AUTH) -f internal/authclustertests/docker-compose.yml down
# Install Pilosa
install:

2
api.go
View file

@ -247,7 +247,7 @@ func (api *API) CreateIndex(ctx context.Context, indexName string, options Index
}
// Create index.
index, err := api.holder.CreateIndexAndBroadcast(cim)
index, err := api.holder.CreateIndexAndBroadcast(ctx, cim)
if err != nil {
return nil, errors.Wrap(err, "creating index")
}

View file

@ -69,6 +69,7 @@ type InternalClient interface {
IngestNodeOperations(ctx context.Context, uri *pnet.URI, indexName string, ireq *ingest.ShardedRequest) error
IDAllocDataReader(ctx context.Context) (io.ReadCloser, error)
IDAllocDataWriter(ctx context.Context, f io.Reader, primary *topology.Node) error
IndexTranslateDataReader(ctx context.Context, index string, partitionID int) (io.ReadCloser, error)
FieldTranslateDataReader(ctx context.Context, index, field string) (io.ReadCloser, error)
@ -223,6 +224,10 @@ func (n nopInternalClient) IDAllocDataReader(ctx context.Context) (io.ReadCloser
return nil, nil
}
func (n nopInternalClient) IDAllocDataWriter(cctx context.Context, f io.Reader, primary *topology.Node) error {
return nil
}
func (n nopInternalClient) IndexTranslateDataReader(ctx context.Context, index string, partitionID int) (io.ReadCloser, error) {
return nil, nil
}

View file

@ -61,6 +61,8 @@ type Client struct {
shardNodes shardNodes
tick *time.Ticker
done chan struct{}
AuthToken string
}
func (c *Client) getURIsForShard(index string, shard uint64) ([]*pnet.URI, error) {
@ -283,7 +285,7 @@ func (c *Client) Query(query PQLQuery, options ...interface{}) (*QueryResponse,
return nil, errors.Wrap(err, "making request data")
}
path := fmt.Sprintf("/index/%s/query", query.Index().name)
_, respData, err := c.HTTPRequest("POST", path, reqData, defaultProtobufHeaders())
_, respData, err := c.HTTPRequest("POST", path, reqData, c.augmentHeaders(defaultProtobufHeaders()))
if err != nil {
return nil, err
}
@ -306,7 +308,7 @@ func (c *Client) CreateIndex(index *Index) error {
data := []byte(index.options.String())
path := fmt.Sprintf("/index/%s", index.name)
status, body, err := c.HTTPRequest("POST", path, data, nil)
status, body, err := c.HTTPRequest("POST", path, data, c.augmentHeaders(nil))
if err != nil {
return errors.Wrapf(err, "creating index: %s", index.name)
}
@ -330,7 +332,7 @@ func (c *Client) CreateField(field *Field) error {
data := []byte(field.options.String())
path := fmt.Sprintf("/index/%s/field/%s", field.index.name, field.name)
status, body, err := c.HTTPRequest("POST", path, data, nil)
status, body, err := c.HTTPRequest("POST", path, data, c.augmentHeaders(nil))
if err != nil {
return errors.Wrapf(err, "creating field: %s in index: %s", field.name, field.index.name)
}
@ -398,7 +400,7 @@ func (c *Client) DeleteIndexByName(index string) error {
defer span.Finish()
path := fmt.Sprintf("/index/%s", index)
_, _, err := c.HTTPRequest("DELETE", path, nil, nil)
_, _, err := c.HTTPRequest("DELETE", path, nil, c.augmentHeaders(nil))
return err
}
@ -408,7 +410,7 @@ func (c *Client) DeleteField(field *Field) error {
defer span.Finish()
path := fmt.Sprintf("/index/%s/field/%s", field.index.name, field.name)
_, _, err := c.HTTPRequest("DELETE", path, nil, nil)
_, _, err := c.HTTPRequest("DELETE", path, nil, c.augmentHeaders(nil))
return err
}
@ -597,7 +599,7 @@ func (c *Client) fetchFragmentNodes(indexName string, shard uint64) ([]fragmentN
return []fragmentNode{*c.manualFragmentNode}, nil
}
path := fmt.Sprintf("/internal/fragment/nodes?shard=%d&index=%s", shard, indexName)
_, body, err := c.HTTPRequest("GET", path, []byte{}, nil)
_, body, err := c.HTTPRequest("GET", path, []byte{}, c.augmentHeaders(nil))
if err != nil {
return nil, err
}
@ -635,7 +637,7 @@ func (c *Client) fetchPrimaryNode() (fragmentNode, error) {
}
func (c *Client) importData(uri *pnet.URI, path string, data []byte) error {
if status, _, err := c.doRequest(uri, "POST", path, defaultProtobufHeaders(), data); err != nil {
if status, _, err := c.doRequest(uri, "POST", path, c.augmentHeaders(defaultProtobufHeaders()), data); err != nil {
return errors.Wrapf(err, "import to %s", uri.HostPort())
} else if status == http.StatusPreconditionFailed {
return ErrPreconditionFailed
@ -683,7 +685,8 @@ func (c *Client) importRoaringBitmap(uri *pnet.URI, field *Field, shard uint64,
return err
}
status, _, err := c.doRequest(uri, "POST", path, defaultProtobufHeaders(), data)
header := c.augmentHeaders(defaultProtobufHeaders())
status, _, err := c.doRequest(uri, "POST", path, header, data)
if err != nil {
return errors.Wrapf(err, "roaring import to %s, status: %d", uri.HostPort(), status)
}
@ -724,7 +727,7 @@ func (c *Client) Info() (Info, error) {
span := c.tracer.StartSpan("Client.Info")
defer span.Finish()
_, data, err := c.HTTPRequest("GET", "/info", nil, nil)
_, data, err := c.HTTPRequest("GET", "/info", nil, c.augmentHeaders(nil))
if err != nil {
return Info{}, errors.Wrap(err, "requesting /info")
}
@ -754,7 +757,7 @@ func (c *Client) Status() (Status, error) {
}
func (c *Client) readSchema() ([]SchemaIndex, error) {
_, data, err := c.HTTPRequest("GET", "/schema", nil, nil)
_, data, err := c.HTTPRequest("GET", "/schema", nil, c.augmentHeaders(nil))
if err != nil {
return nil, errors.Wrap(err, "requesting /schema")
}
@ -1021,6 +1024,9 @@ func (c *Client) augmentHeaders(headers map[string]string) map[string]string {
version := strings.TrimPrefix(Version, "v")
headers["User-Agent"] = fmt.Sprintf("pilosa/client/%s", version)
if c.AuthToken != "" {
headers["Authorization"] = c.AuthToken
}
return headers
}
@ -1177,7 +1183,7 @@ func (c *Client) startTransaction(id string, timeout time.Duration, exclusive bo
return nil, errors.Wrap(err, "marshalling transaction")
}
status, data, err := c.httpRequest("POST", "/transaction", bod, defaultJSONHeaders(), true)
status, data, err := c.httpRequest("POST", "/transaction", bod, c.augmentHeaders(defaultJSONHeaders()), true)
if status == http.StatusConflict && time.Now().Before(deadline) {
// if we're getting StatusConflict after all the usual timeouts/retries, keep retrying until the deadline
time.Sleep(time.Second)
@ -1204,7 +1210,7 @@ func (c *Client) startTransaction(id string, timeout time.Duration, exclusive bo
}
func (c *Client) FinishTransaction(id string) (*pilosa.Transaction, error) {
_, data, err := c.httpRequest("POST", "/transaction/"+id+"/finish", nil, defaultJSONHeaders(), true)
_, data, err := c.httpRequest("POST", "/transaction/"+id+"/finish", nil, c.augmentHeaders(defaultJSONHeaders()), true)
if err != nil && len(data) == 0 {
return nil, err
}
@ -1226,7 +1232,7 @@ func (c *Client) FinishTransaction(id string) (*pilosa.Transaction, error) {
}
func (c *Client) Transactions() (map[string]*pilosa.Transaction, error) {
_, respData, err := c.httpRequest("GET", "/transactions", nil, defaultJSONHeaders(), true)
_, respData, err := c.httpRequest("GET", "/transactions", nil, c.augmentHeaders(defaultJSONHeaders()), true)
if err != nil {
return nil, errors.Wrap(err, "getting transactions")
}
@ -1240,7 +1246,7 @@ func (c *Client) Transactions() (map[string]*pilosa.Transaction, error) {
}
func (c *Client) GetTransaction(id string) (*pilosa.Transaction, error) {
_, data, err := c.httpRequest("GET", "/transaction/"+id, nil, defaultJSONHeaders(), true)
_, data, err := c.httpRequest("GET", "/transaction/"+id, nil, c.augmentHeaders(defaultJSONHeaders()), true)
if err != nil {
return nil, err
}

View file

@ -31,5 +31,6 @@ Backs up a FeatureBase server to a local, tar-formatted snapshot file.
flags.DurationVar(&cmd.RetryPeriod, "retry-period", cmd.RetryPeriod, "Length of time after HTTP request failure to continue retrying request.")
flags.StringVar(&cmd.Pprof, "pprof", cmd.Pprof, "host:port to listen for profiling requests at /debug/pprof and /debug/fgprof.")
ctl.SetTLSConfig(flags, "", &cmd.TLS.CertificatePath, &cmd.TLS.CertificateKeyPath, &cmd.TLS.CACertPath, &cmd.TLS.SkipVerify, &cmd.TLS.EnableClientVerification)
flags.StringVar(&cmd.AuthToken, "auth-token", "", "Authentication token")
return ccmd
}

View file

@ -5,7 +5,7 @@ import (
"context"
"io"
"github.com/molecula/featurebase/v3"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/ctl"
"github.com/spf13/cobra"
)
@ -51,6 +51,7 @@ omitted. If it is present then its format should be YYYY-MM-DDTHH:MM.
flags.BoolVarP(&Importer.CreateSchema, "create", "e", false, "Create the schema if it does not exist before import.")
flags.BoolVarP(&Importer.Clear, "clear", "", false, "Clear the data provided in the import.")
ctl.SetTLSConfig(flags, "", &Importer.TLS.CertificatePath, &Importer.TLS.CertificateKeyPath, &Importer.TLS.CACertPath, &Importer.TLS.SkipVerify, &Importer.TLS.EnableClientVerification)
flags.StringVar(&Importer.AuthToken, "auth-token", "", "Authentication token")
return importCmd
}

View file

@ -27,6 +27,7 @@ The Restore command will take a backup archive and restore it to a new, clean cl
flags.IntVar(&cmd.Concurrency, "concurrency", 1, "number of concurrent uploads")
flags.DurationVar(&cmd.RetryPeriod, "retry-period", cmd.RetryPeriod, "Length of time after HTTP request failure to continue retrying request.")
flags.StringVar(&cmd.Pprof, "pprof", cmd.Pprof, "host:port to listen for profiling requests at /debug/pprof and /debug/fgprof.")
flags.StringVar(&cmd.AuthToken, "auth-token", "", "Authentication token")
ctl.SetTLSConfig(
flags, "",
&cmd.TLS.CertificatePath,

View file

@ -52,6 +52,8 @@ type BackupCommand struct { // nolint: maligned
*pilosa.CmdIO
TLS server.TLSConfig
AuthToken string
}
// NewBackupCommand returns a new instance of BackupCommand.
@ -93,6 +95,10 @@ func (cmd *BackupCommand) Run(ctx context.Context) (err error) {
}
cmd.client = client
if cmd.AuthToken != "" {
ctx = context.WithValue(ctx, "token", "Bearer "+cmd.AuthToken)
}
// Determine the field type in order to correctly handle the input data.
indexes, err := cmd.client.Schema(ctx)
if err != nil {

View file

@ -54,6 +54,8 @@ type ImportCommand struct { // nolint: maligned
*pilosa.CmdIO
TLS server.TLSConfig
AuthToken string
}
// NewImportCommand returns a new instance of ImportCommand.
@ -84,6 +86,10 @@ func (cmd *ImportCommand) Run(ctx context.Context) error {
}
cmd.client = client
if cmd.AuthToken != "" {
ctx = context.WithValue(ctx, "token", "Bearer "+cmd.AuthToken)
}
if cmd.CreateSchema {
if cmd.FieldOptions.Type == "" {
// set the correct type for the field

View file

@ -9,12 +9,17 @@ import (
"io"
"io/ioutil"
"net/http"
"os"
"reflect"
"strings"
"testing"
"time"
"github.com/molecula/featurebase/v3"
"github.com/golang-jwt/jwt"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/authn"
"github.com/molecula/featurebase/v3/logger"
"github.com/molecula/featurebase/v3/server"
"github.com/molecula/featurebase/v3/test"
"github.com/molecula/featurebase/v3/testhook"
)
@ -568,3 +573,156 @@ func TestImportCommand_RunBool(t *testing.T) {
}
})
}
func TestImport_AuthOn(t *testing.T) {
clusterSize := 1
logFilename := "./testdata/query.log"
_, err := os.Create(logFilename)
if err != nil {
t.Fatalf("Failed to create query log file: %s", err)
}
auth := server.Auth{
Enable: true,
ClientId: "e9088663-eb08-41d7-8f65-efb5f54bbb71",
ClientSecret: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
AuthorizeURL: "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/authorize",
TokenURL: "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/token",
GroupEndpointURL: "https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true",
LogoutURL: "https://login.microsoftonline.com/common/oauth2/v2.0/logout",
Scopes: []string{"https://graph.microsoft.com/.default", "offline_access"},
SecretKey: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
RedirectBaseURL: "https://localhost:0",
QueryLogPath: logFilename,
PermissionsFile: "./testdata/permissions.yaml",
}
commandOpts := make([][]server.CommandOption, clusterSize)
configs := make([]*server.Config, clusterSize)
for i := range configs {
conf := server.NewConfig()
configs[i] = conf
conf.Bind = "https://localhost:0"
conf.Auth = auth
conf.TLS.CertificatePath = "./testdata/certs/localhost.crt"
conf.TLS.CertificateKeyPath = "./testdata/certs/localhost.key"
conf.TLS.CACertPath = "./testdata/certs/pilosa-ca.crt"
conf.TLS.EnableClientVerification = false
conf.TLS.SkipVerify = true
commandOpts[i] = append(commandOpts[i], server.OptCommandConfig(conf))
}
a, err := authn.NewAuth(
logger.NewStandardLogger(os.Stdout),
"http://localhost:0/",
auth.Scopes,
auth.AuthorizeURL,
auth.TokenURL,
auth.GroupEndpointURL,
auth.LogoutURL,
auth.ClientId,
auth.ClientSecret,
auth.SecretKey,
)
if err != nil {
t.Fatal(err)
}
// make a valid token
tkn := jwt.New(jwt.SigningMethodHS256)
claims := tkn.Claims.(jwt.MapClaims)
groupString, _ := authn.ToGob64([]authn.Group{{GroupID: "group-id-test", GroupName: "group-name-test"}})
claims["molecula-idp-groups"] = groupString
claims["oid"] = "42"
claims["name"] = "valid"
token, err := tkn.SignedString([]byte(a.SecretKey()))
if err != nil {
t.Fatal(err)
}
validToken := "Bearer " + token
invalidToken := "Bearer " + string(tkn.Raw)
tests := []struct {
Index string
Field string
CreateSchema bool
Token string
Err error
}{
{
Index: "test",
Field: "field1",
CreateSchema: true,
Token: validToken,
Err: nil,
},
{
Index: "test",
Field: "field1",
CreateSchema: false,
Token: validToken,
Err: nil,
},
{
Index: "test",
Field: "field1",
CreateSchema: false,
Token: invalidToken,
Err: fmt.Errorf("token contains an invalid number of segments"),
},
{
Index: "test",
Field: "field1",
CreateSchema: true,
Token: invalidToken,
Err: fmt.Errorf("token contains an invalid number of segments"),
},
}
t.Run("set", func(t *testing.T) {
buf := bytes.Buffer{}
stdin, stdout, stderr := GetIO(buf)
cm := NewImportCommand(stdin, stdout, stderr)
file, err := testhook.TempFile(t, "import.csv")
if err != nil {
t.Fatalf("creating tempfile: %v", err)
}
_, err = file.Write([]byte("1,2\n3,4\n5,6"))
if err != nil {
t.Fatalf("writing to tempfile: %v", err)
}
if err != nil {
t.Fatal(err)
}
cluster := test.MustRunCluster(t, clusterSize, commandOpts...)
defer cluster.Close()
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
for i, test := range tests {
cm.Index = test.Index
cm.Field = test.Field
cm.CreateSchema = test.CreateSchema
cm.Paths = []string{file.Name()}
ctx := context.WithValue(context.Background(), "token", test.Token)
err = cm.Run(ctx)
if test.Err != nil {
if !strings.Contains(err.Error(), test.Err.Error()) {
t.Fatalf("Test: %d, Import Run doesn't work: got %s, expected: %s", i, err, test.Err)
}
} else {
if err != test.Err {
t.Fatalf("Test: %d, Import Run doesn't work: got %s, expected: %s", i, err, test.Err)
}
}
}
err = os.Remove(logFilename)
if err != nil {
t.Fatalf("Failed to delete query log file: %s", err)
}
})
}

View file

@ -29,7 +29,8 @@ import (
// RestoreCommand represents a command for restoring a backup to
type RestoreCommand struct {
tlsConfig *tls.Config
Host string
Host string
Concurrency int
@ -47,7 +48,10 @@ type RestoreCommand struct {
// Standard input/output
*pilosa.CmdIO
TLS server.TLSConfig
AuthToken string
}
// NewRestoreCommand returns a new instance of RestoreCommand.
@ -88,6 +92,10 @@ func (cmd *RestoreCommand) Run(ctx context.Context) (err error) {
}
cmd.client = client
if cmd.AuthToken != "" {
ctx = context.WithValue(ctx, "token", "Bearer "+cmd.AuthToken)
}
nodes, err := cmd.client.Nodes(ctx)
if err != nil {
return err
@ -139,8 +147,24 @@ func (cmd *RestoreCommand) restoreSchema(ctx context.Context, primary *topology.
if len(existingSchema) == 0 {
cmd.Logger().Printf("Load Schema")
url := primary.URI.Path("/schema")
req, err := retryablehttp.NewRequest("POST", url, f)
if err != nil {
return err
}
req = req.WithContext(ctx)
req.Header.Add("Accept", "application/json")
token, ok := ctx.Value("token").(string)
if ok && token != "" {
req.Header.Set("Authorization", token)
}
client := cmd.newClient()
_, err = client.Post(url, "application/json", f)
_, err = client.Do(req)
if err != nil {
return err
}
} else {
schema := &pilosa.Schema{}
if err := json.NewDecoder(f).Decode(schema); err != nil {
@ -222,10 +246,9 @@ func (cmd *RestoreCommand) restoreIDAlloc(ctx context.Context, primary *topology
defer f.Close()
logger.Printf("Load idalloc")
url := primary.URI.Path("/internal/idalloc/restore")
client := cmd.newClient()
_, err = client.Post(url, "application/octet-stream", f)
err = cmd.client.IDAllocDataWriter(ctx, f, primary)
return err
}
@ -301,6 +324,11 @@ func (cmd *RestoreCommand) restoreShard(ctx context.Context, filename string) er
req = req.WithContext(ctx)
req.Header.Set("Content-Type", "application/octet-stream")
token, ok := ctx.Value("token").(string)
if ok && token != "" {
req.Header.Set("Authorization", token)
}
client := cmd.newClient()
resp, err := client.Do(req)
if err != nil {

12
ctl/testdata/certs/README.md vendored Normal file
View file

@ -0,0 +1,12 @@
# these test certs were generated with the following commands
certstrap --depot-path certs init --common-name pilosa-ca --expires "100 years"
certstrap --depot-path certs request-cert --common-name localhost --domain localhost
certstrap --depot-path certs sign "localhost" --CA pilosa-ca --expires "100 years"
# certstrap version
dev-25ea708a
(built with go 1.13)

25
ctl/testdata/certs/localhost.crt vendored Normal file
View file

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEPjCCAiagAwIBAgIRAJ7rl74WPv8pLuhVRXt6fV0wDQYJKoZIhvcNAQELBQAw
FDESMBAGA1UEAxMJcGlsb3NhLWNhMCAXDTIwMTAyMDE5MTMzNFoYDzIxMjAxMDIw
MTkxMzE5WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDmi8FMWt23M0Cr2aCgEXUGQ0gv/4M7CXH/5GkSI866YwGV
Bd1iZMBRiONQwvGDnqYZRrAQv6mFjfyBqxdkbh++74FC3JK7sLhks0vg5VwbHV7T
5kj3bJqd+LKn5qPPOQXX9sgmv/NkggF/XXwF73noLPmgDQ78S+OP0ANmi1TQiU3a
gE+qp+Qpl5KC7dH9aC9nvE9iGfEcGNr+rXj05liiXqe4ZtIKWjeke7Ej64C6qX97
bNPzmLARtqbRsIkfAU8SJy3YuHfW8n1xr4B7ENm9jHQCh1wUv2YhaPpnio+/R2zp
Lw4yCqilDX9ZZ4nG3cBFuziSf+BUXJ9ydbw1aX8HAgMBAAGjgYgwgYUwDgYDVR0P
AQH/BAQDAgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4E
FgQUJRQJpaR5bp4ZyUsMxgK+UJl6PSgwHwYDVR0jBBgwFoAU69lmXSa5BZeyYU/6
XpWdtr59H1YwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IC
AQChxsBZ/b14ukJXX48BxAyZcy5r7GrLcRGQ3guUTONFVDWPzzpd8mjHi0yJDhMW
2zWtw3/H+c+zT7uRd+2sUxFdpAurNSFCdV++5Q/0aFvl+By5+MhVhtznEQDU0/lM
zFxiEYe/N9Vi2N0S1KPxvYL/RfBU27u+O/50zhjueM1BTyHTTqL6E2DFeT2VPKIg
zCDUtiTEDFZrD0XGITT/3CIoNCK8aC+Fq65OEoyEn6qR5qg1Kc4tfZmo6hWYiSlR
XeP36cP9R8kEMte1BdE74GVqE9cTuVZERdgB0hv3EME7Byq7uIm/a+JXbsh2/OFm
HcE0/HP+O0YK8YaVMGwI3pZYy2syWqPcakcvusETehr6P+Ihh2cOKRwqkCl6b87e
uSLJNTUMKZgakW6Bjv6lgQaWqnKzTC/RgmQ+G3w0nKATX9+jYE2j3MzZhbtcml+2
gp6u225yAJaYt/MQidwUMiKYeCgjaUNoL0fOJesGkokPk80ceISnqvbSRiZRTvK1
bVenkhkBrHuvvgKVstzcuZI9oQ2snWhK1naVQiOtQNEFUCHwyU95zADOK0km88NB
2het6yYaEUL9csHPEjPd3lFglerGQnil2Ly1slUC4jb7hfVRHjOFs8PVr9gQ45dW
Jvsv4pawHKFE0ennoNvoDmzbiY1TY5ScTZquPGIsEBV+tQ==
-----END CERTIFICATE-----

16
ctl/testdata/certs/localhost.csr vendored Normal file
View file

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICgDCCAWgCAQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA5ovBTFrdtzNAq9mgoBF1BkNIL/+DOwlx/+RpEiPO
umMBlQXdYmTAUYjjUMLxg56mGUawEL+phY38gasXZG4fvu+BQtySu7C4ZLNL4OVc
Gx1e0+ZI92yanfiyp+ajzzkF1/bIJr/zZIIBf118Be956Cz5oA0O/Evjj9ADZotU
0IlN2oBPqqfkKZeSgu3R/WgvZ7xPYhnxHBja/q149OZYol6nuGbSClo3pHuxI+uA
uql/e2zT85iwEbam0bCJHwFPEict2Lh31vJ9ca+AexDZvYx0AodcFL9mIWj6Z4qP
v0ds6S8OMgqopQ1/WWeJxt3ARbs4kn/gVFyfcnW8NWl/BwIDAQABoCcwJQYJKoZI
hvcNAQkOMRgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQAD
ggEBABMi2/4j1/qzwWAYlEs2KW3z+apzzDLKgjE0kY6QvELh/8aBj0rMglb0HM2x
4iSSoX1ZwZgDZ9fIJ3klG/UF7CUweMghb9yC2PP9Z8WuqaECQyM87KgSln8PND9E
1OvD30rp9yr9KxEeckq+c1ebLi/qGrIY21VCwfxA0mv3sfi7Q5ONIckay/Xj+1Tz
ovE/TkM/8wTE/SKbpQSCkP7K1NDXuAhMGjcN0x3d3f8nBcLcZOrRroiHy38Bv/9T
Vd62IY6uqYw9sluBbMX72D/mmJiCKEw3+DhDJFhHCTCrAQM0QwLuwnG2lQFYoENc
ZAkwDIi+3DXHEEyloNSYGtXMEiA=
-----END CERTIFICATE REQUEST-----

27
ctl/testdata/certs/localhost.key vendored Normal file
View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA5ovBTFrdtzNAq9mgoBF1BkNIL/+DOwlx/+RpEiPOumMBlQXd
YmTAUYjjUMLxg56mGUawEL+phY38gasXZG4fvu+BQtySu7C4ZLNL4OVcGx1e0+ZI
92yanfiyp+ajzzkF1/bIJr/zZIIBf118Be956Cz5oA0O/Evjj9ADZotU0IlN2oBP
qqfkKZeSgu3R/WgvZ7xPYhnxHBja/q149OZYol6nuGbSClo3pHuxI+uAuql/e2zT
85iwEbam0bCJHwFPEict2Lh31vJ9ca+AexDZvYx0AodcFL9mIWj6Z4qPv0ds6S8O
MgqopQ1/WWeJxt3ARbs4kn/gVFyfcnW8NWl/BwIDAQABAoIBAFX+GPqfBgY4cs3m
3ff2qvzMCdgFaXCS5Fe7XcmrW4fAOC3awynZRLbk5U0Reb5LZc8Vw8RriRLM1DuV
kqMeRG8WrNNArOafUxgUnJ/lTUa73MwTIHJRqxZzVkg0SjOYJGranOt/O4zoxSA5
wXIBUipc5Dtjw4wtzlKtFyefnuItL2MCdwOHUdZfnhr9Oykp1fuNqBqkkeryj3XV
ukHQvqU5zkMSayprNglziqTHUzU33iyZeDng+CJQeYTEc7Gn+zja2SFFBlPHqXXo
/OzAr94zI3vOnj3yRM3+sKMJVPV+RoJEGpsvPVuVn38d1VnIMEx8Gy/wif6tmM9c
7Q44hKECgYEA/JMFwkPGbry80ktDI065k5FIYn1EDRyUaQqyskmkBRcNW3qOShqj
o/zWQfCgxP587IEdKBBwqCpdqfghi3EW+JqfVlbGY6t1chAurYF/47CTIgKO5qRM
GdCY2OdiAeo5nba/KiLQfSuY08MCNDrQabLRJXIng8qVWpRwQzsv5rECgYEA6aw/
HugeQhTxk2uV91jJaAQIaxrt6JxuoG0CGGlbDrrTl2dnbPYA0muHMFdT/bzKjCpv
n/ScqbCyHuy+lWSnOzgedRNQCB46+0H58LAjITAj9QaT3raZqVReVIaD+pnx27dp
Cw5Ws6ENa9AQey3DO+dkRWot2AcLSw6TGR8HnzcCgYEA/ArfCU/G2cSgDJ6sPbSW
vaqR+C6W1Rq7AuN5FS8lbSrm2m2/RjW1LLTnPmAYntxx3zSs2sklErtMQovpNZRB
3w21iVwIl3eHOK7rVZtP+u++s4aoAYLcqjod/P1RMSYCHt85fpvFP9Ncq50DOwmh
5ohZ6ysyQXLMfdp4+K48i9ECgYEApOFALKu2ZgRnLRFV4REKFFX8Jq76vg5bVOF2
AAmfEbasBIIXDWBL1i2/V1HXVwv2k46B8wjj3ixqkr2UAM/j3DpN62g0KXZDQfUc
ykNOlmVkickZX6XSqRN5+ARubc5gRRuWiBGXBeqXEMLgTjpNLyCntP8l1++ofU6M
ZsZpV2MCgYA3nfNXAR5O4B/dm/2HmDQrXy0qia7Hwi/95pgL2FJaEmjBCPI1j12o
M5YCbhpr1pwsNKPV9AUlUz+OCwS8Vt+V0gQf9/XvNOsifU+mbMYVpuNGwmcKafnv
qECSeidrmhWJSR/SSNBcE94im/8ObVU110WJMkjC9otjDl9Aua/3LQ==
-----END RSA PRIVATE KEY-----

16
ctl/testdata/certs/pilosa-ca.crl vendored Normal file
View file

@ -0,0 +1,16 @@
-----BEGIN X509 CRL-----
MIIChTBvAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCXBpbG9zYS1jYRcN
MjAxMDIwMTkxMzIyWhgPMjEyMDEwMjAxOTEzMjBaMACgIzAhMB8GA1UdIwQYMBaA
FOvZZl0muQWXsmFP+l6Vnba+fR9WMA0GCSqGSIb3DQEBCwUAA4ICAQBja+EDQAp+
KeD7UhWMMrTd9j03GgQ2E2Z7+Ba0qJ5+kS7/t+Yja2o5dQJkrC3GwEMOQb6DRRUE
nUE4xlr5Rryoq0dZk+Lp1f4cHrnP8l1xylUL44gsnY4v8zMR8L8X98vj7kKCqB8w
DFX7qkMlE5Ie2Hha7uuOJ85FnxIbMcRxFQH2m2zDfWG8/Lmxezvv9Hn45V/kwIQy
MmBh6cNuhzEneyNpM9yMRe/29QgVitF/2q6d+FzK8w8hkUFeYlyM+cP7F4Ml7160
UidSQM04zvBtJ8frZAvrDaPBBZhrTXcyw6+Qnp/aaW1ZsEIdHEcbYGNdgtazleoG
VH35cDP90KfiRbq69PQ9Zqn3cI//MX3sHrglA9wsEhHc9P7dowHaOFyxPouZPEmQ
/Jqg5oyJzujRwhf0v3SdJvhuDEzla2N+QyYRk0kRHtdv+glz7T7CnTYCk+DTv+oh
QABUrCbjfBoE5M2Qep9ZkIbl2gaDCpvbZSF4zFLKQc2aIOBpVn3HgTGBvdFD3FJY
Txl2F4Y3rS1T/WMAH86cZIc9h5HlMdFtAFnHAlHtB3wGw3FD/GcvGcvz2D4GaxKq
erzrnOxjYOA4M0haGzWF6dC7aPA8y35eZuqNXvbenTtc7A11bWTJfG1I7ctvLyPE
MpCNMHfymh/XtYZiZhvu6ueu3OeKScN+tA==
-----END X509 CRL-----

29
ctl/testdata/certs/pilosa-ca.crt vendored Normal file
View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE6jCCAtKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlwaWxv
c2EtY2EwIBcNMjAxMDIwMTkxMzIyWhgPMjEyMDEwMjAxOTEzMjBaMBQxEjAQBgNV
BAMTCXBpbG9zYS1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALT/
uNmbnfXWNX+FsL0Waqw/5deti5F4cSjMrGRQpXxalTcooqNk/lkeqXkvi9ooFROZ
/HyQR9GM9dSD/aj6gD3FnGA4ueB24Xr6bWsRpDRh6+3UGLB3YCNNdGLSfX3LPMYh
RJutFmsg+r6SrSytbLbffu+0a/4fxtajZNwQJjDjd8qflXQZYlzp2LHk1A/jqqdI
fBtqkNg925TGKiavvUqKtdI/eFzRoiQ7NLBUJmszzveUXvOUMsMnW2/myLBe3Oqk
Vsy85lya0ADln20C3Lb0+ZA4KoGX3EWdtBXEuWqMoyvCJoJ4I3bH2LlfOUjRt8UE
pPk6sPMROJ+75mlgvgnSlYsN8PaZdvdm2VGVWRWUyEfyW/qa2fv8d2XBWqibl0YF
tqay9CX1aWgC9q12yx3vj7Yh+ZNbeZFLc7IL8zyNMwIjIOIIyGBY70KewfgVktzq
fAMz6h1sr9Kxozil97Cu3ma4B6UiL3rUbYMO/rNhVxcIuUoJpgIEVuRt+uXEG74y
XftauZ67qILFQzfpoacncvDEx5nJ3itLgbbyt1n1iWdGuEiMSLFT+x+nNUgVpQgA
sWRYxHdisM4xzRVN6pAaToMs1p8Ju7l9xU3z7RSogTyVk9gMIIV4t9TDYP5fI10Y
GFi7B6q0t3pIGXgKySHjCSl0EKYkDQDFl5tWeaiXAgMBAAGjRTBDMA4GA1UdDwEB
/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTr2WZdJrkFl7Jh
T/pelZ22vn0fVjANBgkqhkiG9w0BAQsFAAOCAgEAnlBFrWhB+WesCc3lhK980rA6
roNFYMZdaXvg4zaEGergkRvPab5yXoof1AAeznJm45GQfXn8HbQlrZmAqWg3fNld
/TX+jNvosM8K8K+PzesDGHsm/eQnbrb0qzMDsQgFY+nnD+x/ZQtjmKZtcNr/0ZlM
EJeXWU5cGy70GMbNztspMHsOLa3ZDLsBOJYOwSFxDlLDFrjZoRoPCWw8jRL+Tb4t
JjZcGZDD4a5+DqcojanIdNU1yI4teP6aV1LQTVNn4pwOap+tD0De/WzOPmXTQq5M
9ssxL7xSqVShQQMC8LVSWSRxtT6kLq0Av6i7wio0DZGnH3ynERTUs13DRZkwbVsE
OaQLmiQnsHRTIpdts/fswZ2FRPvdhhXxBjiGQZGEGXXznxHTNJ6nQioh95Ft5hNA
82i8Z74miaFIT/33/sZ5SuwUzphCgqCY2x7NUS8J313O9lsar0bweJTvQaZg/69E
PmmwUcDebh+pgKP01z4BqTzhtchmFUKzT+oOC8tmTeSlhsBTzO4xMw6OqhpXKL+c
k9f2CGUZYtEZHDRmP+C++FEi+B/tV2Oq3on+QPiaIIcRsOftthGUvJ8htUl3w+hq
B5TnL8CeLjXGKKRp+UiakrB4E7y2aIbrtIRnJ/Llg2XMND/0xbldsNsyDNXCIoDH
sz8HqwF3CUbv5XD4ioY=
-----END CERTIFICATE-----

51
ctl/testdata/certs/pilosa-ca.key vendored Normal file
View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAtP+42Zud9dY1f4WwvRZqrD/l162LkXhxKMysZFClfFqVNyii
o2T+WR6peS+L2igVE5n8fJBH0Yz11IP9qPqAPcWcYDi54HbhevptaxGkNGHr7dQY
sHdgI010YtJ9fcs8xiFEm60WayD6vpKtLK1stt9+77Rr/h/G1qNk3BAmMON3yp+V
dBliXOnYseTUD+Oqp0h8G2qQ2D3blMYqJq+9Soq10j94XNGiJDs0sFQmazPO95Re
85Qywydbb+bIsF7c6qRWzLzmXJrQAOWfbQLctvT5kDgqgZfcRZ20FcS5aoyjK8Im
gngjdsfYuV85SNG3xQSk+Tqw8xE4n7vmaWC+CdKViw3w9pl292bZUZVZFZTIR/Jb
+prZ+/x3ZcFaqJuXRgW2prL0JfVpaAL2rXbLHe+PtiH5k1t5kUtzsgvzPI0zAiMg
4gjIYFjvQp7B+BWS3Op8AzPqHWyv0rGjOKX3sK7eZrgHpSIvetRtgw7+s2FXFwi5
SgmmAgRW5G365cQbvjJd+1q5nruogsVDN+mhpydy8MTHmcneK0uBtvK3WfWJZ0a4
SIxIsVP7H6c1SBWlCACxZFjEd2KwzjHNFU3qkBpOgyzWnwm7uX3FTfPtFKiBPJWT
2AwghXi31MNg/l8jXRgYWLsHqrS3ekgZeArJIeMJKXQQpiQNAMWXm1Z5qJcCAwEA
AQKCAgAWmjiDNCOtp2pW2mMPudToXbJeFJXxPJEk/yon/MotlUI8+R4WOW5pwqJ3
N7DHNWosYHZfN8VALdIlD7aFe4K4NA0rFupfVXki2lL/o9xVjkTgFjRfFQk0X1/B
V3fEVbTpKQ5gQmUiS6QEWFy3z5Bb5dz8IhO6UE2MUCswL/QU9tLmwrbvIJxf7fPZ
gzHYKh4NdcfJxK0B0/evxG9PFXMV8+xwrOxi6urMi3gw7NE/YeDemfChikgshqWs
e61kGPSNeKg+OPirZ8nB0urtugXF8yGXGOx18njXWLI8Zayh2Z4mwL/+WvJSyvIN
dA67QTUprULMvL+MGwJvMA+96Q7SBRVKR9HHNaP9pFsZup3QX9mqDQ9miB6+rzn7
f5RiSLVgq+HUPMPfqgXCkQZBcY28TcM1BZhS4uJJTkbvVTkrlHKJs6Q/LBqJFvq0
3+2M1xQb4HdRTRlwZ/YsxdqXGIoA3Xx3nZbb6LlPp/MT93xxsLJNNP47n445Cw8i
lz7hJJDwo+TyXmRRWKlFXO8TEhqKhK9ZEXmkXBxSeCQV0oTYS5kU9XdZ5iu5/CQQ
Lv+uFQfHTPWm/Lp5RC8JEEJwK5bwRAs5d9oWg5EbWJp2ol36g3YO1np5RFsQn3jL
qJPz35X3Bp9zQeZcAZpt1fWFdyX9f7V2LLCY8gUyIlfCEXBWwQKCAQEA8Uoes4ph
15tM1LswmOLqOq9iWuvcKYSNcz8my8nlP5zkUdGKvGcLblm5Mg4wKoPFiyjKhX7I
S8DUN8x7E5aiNBiZ0PGku4CKjubQgdFG/rYRfnrUEaA81Uw3a69eYioKbxhhxDzb
Lqd0/tGNHQZBQEyofOChwqTWpAdIh79F1oXejcPzRDr+oXxJVnvJDdYCEN/0TYkz
qeJdEtnVf1x2oNjuPIRuZNldpSDmiUce4QXG/qKgJcQNZ+paDDFK/G/rXHcIvV5C
du9yxxfppY7fRRMm+LFqDhKEWveG4OUhUgut71J0EREO65oMbf6UcZ2FS4XDTbFD
RSO4d8bKgf2eKwKCAQEAwAigvp8qq/yYitZeXhI6cI4ztSvwqblREhUgYWyrbUFo
a38Bey1fKQzJYUYA7raFU6alRHoHBQywjANhIaLlfvLtQfuZ4Z9CGP3Qn33uQR/E
ha4MNjjwUB0jx9lsDze1h61V95fxQLGNLVwGoaES4BpDRqvYJKnQv2X0SUigEg5U
GwryNlEW0AS/Xp/k7+PGJQernHIEWYS70FleHbAiINh+lzSfbJObgd6XnQ8IxtTr
xthXBKkkNBJdJX+/3qUQgOxTjSNUY4N9Np7myFfMvcAXuR7/K7bDegwHxffYb6Gc
v3fCFoTQFn1KTh0IvRjyv3WzqInAYVjC8CpD562VRQKCAQBirI4LnE7Q7lioMnj4
POvO3gRZ7FSXwfZap/vEoScYMaAJeajDzVwWX6jluHmoGUVC2IahuyxMFmpy+zNl
2lcw+NKGaRuV9kYzlF62iBABgBF9aNuq7Z2TGN0dM5VkjY7AyfbJWp3D4YVt4+JS
eUlb8z1//BkK0YBZigT2RplX1l0iGn00bO/OuFYBgRPCjb9AiWWOA8rV8ZVgbSbr
M7PrqWsb4oiGw4GRUvgUMbqGCWfMoFLfvuJAmc0DaXEh9N8KbD9tuctyeg+1LalG
JDxYMjHgyCT35kisLsfA1tMei1oxIcYHaLNyVAg7Pz4TjHiDXwt0jUZWUvpQOUJ9
kGsLAoIBAQCOqFoyAjBDIB16VpI4NDZx01IabxAUJfVSB5vMhFw9h++4m9tP1H7z
Eeqwdr7Ol40ofY4c9sIsQCcPfJs1z7vJuVIESJMih5sk0bmgIn9SpfTqkkfEKDxu
Z5djKeQa0fnrVxucGaZBtyT343uRqwVIsnn0EEk7w2OuLGFz553yi+5zQIh7TXYz
BrPb6dC7XWyfqbkVOaZ9khusRhei2mwgFnTEg3VDxcwqiF/9b2PHwfl9+M18SuL4
RAQqjWLOVbWS8P2Ixgw0+UOVxioP/xm8hO2auqo5oUZKbpF/wgVpuJenraHj9LpZ
Wq5OpUcOo3ACR8A1nk/qgXQf0mYrwEo5AoIBAHEqA2eJVZnPiAs6U7QPAavnLxt/
v0GLzsBBixSV8ErMToN1wfYtBb1t5fgF0Fuy85dREp1CsGJMgrnPX5bCnBmDaLl2
Z1lUaSDcFCu+yXo+Kuy7JvSKZ4++q4ggrHvK8y8FdKH4H+56vTdXe2i9RY/v48g4
kKyNiYtVXxrd/h47WbHF5eApheblH9hH6zC5tB/rW7Hh0nmnDcfmMW4BggbyBinH
MF3jO0YaspZOtRc2xSj8E3sGtN+f/KrBbKBb4J0j7VzuFmZC1u5grl/hx0cYE2ek
HGifmIjkKv5R4xPELoAJZyFOpN1PfS3Y+SOn0mF+RJRoGqMGcQWA3I77b5M=
-----END RSA PRIVATE KEY-----

4
ctl/testdata/permissions.yaml vendored Normal file
View file

@ -0,0 +1,4 @@
user-groups:
"group-id-test":
"test": "write"
admin: "group-id-test"

View file

@ -1083,7 +1083,7 @@ func (h *Holder) LoadView(index, field, view string) (*view, error) {
// CreateIndexAndBroadcast creates an index locally, then broadcasts the
// creation to other nodes so they can create locally as well. An error is
// returned if the index already exists.
func (h *Holder) CreateIndexAndBroadcast(cim *CreateIndexMessage) (*Index, error) {
func (h *Holder) CreateIndexAndBroadcast(ctx context.Context, cim *CreateIndexMessage) (*Index, error) {
h.mu.Lock()
defer h.mu.Unlock()
@ -1093,7 +1093,7 @@ func (h *Holder) CreateIndexAndBroadcast(cim *CreateIndexMessage) (*Index, error
}
// Create the index in etcd as the system of record.
if err := h.persistIndex(context.Background(), cim); err != nil {
if err := h.persistIndex(ctx, cim); err != nil {
return nil, errors.Wrap(err, "persisting index")
}

View file

@ -142,6 +142,14 @@ func NewInternalClientFromURI(defaultURI *pnet.URI, remoteClient *http.Client, o
return ic
}
func AddAuthToken(ctx context.Context, req *http.Request) *http.Request {
token, ok := ctx.Value("token").(string)
if ok && token != "" {
req.Header.Set("Authorization", token)
}
return req
}
// MaxShardByIndex returns the number of shards on a server by index.
func (c *InternalClient) MaxShardByIndex(ctx context.Context) (map[string]uint64, error) {
span, ctx := tracing.StartSpanFromContext(ctx, "InternalClient.MaxShardByIndex")
@ -162,6 +170,7 @@ func (c *InternalClient) maxShardByIndex(ctx context.Context) (map[string]uint64
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -194,6 +203,7 @@ func (c *InternalClient) AvailableShards(ctx context.Context, indexName string)
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -227,6 +237,7 @@ func (c *InternalClient) SchemaNode(ctx context.Context, uri *pnet.URI, views bo
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -258,6 +269,7 @@ func (c *InternalClient) Schema(ctx context.Context) ([]*pilosa.IndexInfo, error
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -294,6 +306,7 @@ func (c *InternalClient) IngestSchema(ctx context.Context, uri *pnet.URI, buf []
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx), giveRawResponse(true))
if err != nil {
@ -344,6 +357,7 @@ func (c *InternalClient) IngestOperations(ctx context.Context, uri *pnet.URI, in
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx))
if err != nil {
@ -376,6 +390,7 @@ func (c *InternalClient) IngestNodeOperations(ctx context.Context, uri *pnet.URI
req.Header.Set("Content-Type", "application/x-protobuf")
req.Header.Set("Accept", "application/x-protobuf")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx))
if err != nil {
@ -404,6 +419,7 @@ func (c *InternalClient) MutexCheck(ctx context.Context, uri *pnet.URI, indexNam
}
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx))
if err != nil {
@ -434,6 +450,7 @@ func (c *InternalClient) PostSchema(ctx context.Context, uri *pnet.URI, s *pilos
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx))
if err != nil {
@ -480,6 +497,7 @@ func (c *InternalClient) CreateIndex(ctx context.Context, index string, opt pilo
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -509,6 +527,7 @@ func (c *InternalClient) FragmentNodes(ctx context.Context, index string, shard
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -540,6 +559,7 @@ func (c *InternalClient) Nodes(ctx context.Context) ([]*topology.Node, error) {
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -590,6 +610,8 @@ func (c *InternalClient) QueryNode(ctx context.Context, uri *pnet.URI, index str
req.Header.Set("Authorization", token)
}
req = AddAuthToken(ctx, req)
req.Header.Set("Content-Length", strconv.Itoa(len(buf)))
req.Header.Set("Content-Type", "application/x-protobuf")
req.Header.Set("Accept", "application/x-protobuf")
@ -682,6 +704,7 @@ func (c *InternalClient) importNode(ctx context.Context, node *topology.Node, in
req.Header.Set("Accept", "application/x-protobuf")
req.Header.Set("X-Pilosa-Row", "roaring")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -902,6 +925,7 @@ func (c *InternalClient) ImportRoaring(ctx context.Context, uri *pnet.URI, index
httpReq.Header.Set("Accept", "application/x-protobuf")
httpReq.Header.Set("X-Pilosa-Row", "roaring")
httpReq.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
httpReq = AddAuthToken(ctx, httpReq)
// Execute request against the host.
resp, err := c.executeRequest(httpReq.WithContext(ctx))
@ -976,6 +1000,7 @@ func (c *InternalClient) exportNodeCSV(ctx context.Context, node *topology.Node,
}
req.Header.Set("Accept", "text/csv")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1018,6 +1043,7 @@ func (c *InternalClient) RetrieveShardFromURI(ctx context.Context, index, field,
}
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1107,6 +1133,7 @@ func (c *InternalClient) CreateFieldWithOptions(ctx context.Context, index, fiel
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1145,6 +1172,7 @@ func (c *InternalClient) FragmentBlocks(ctx context.Context, uri *pnet.URI, inde
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1194,6 +1222,7 @@ func (c *InternalClient) BlockData(ctx context.Context, uri *pnet.URI, index, fi
req.Header.Set("Accept", "application/protobuf")
req.Header.Set("X-Pilosa-Row", "roaring")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx))
if err != nil {
@ -1275,6 +1304,7 @@ func (c *InternalClient) TranslateKeysNode(ctx context.Context, uri *pnet.URI, i
req.Header.Set("Accept", "application/x-protobuf")
req.Header.Set("X-Pilosa-Row", "roaring")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1329,6 +1359,7 @@ func (c *InternalClient) TranslateIDsNode(ctx context.Context, uri *pnet.URI, in
req.Header.Set("Accept", "application/x-protobuf")
req.Header.Set("X-Pilosa-Row", "roaring")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1360,6 +1391,7 @@ func (c *InternalClient) GetNodeUsage(ctx context.Context, uri *pnet.URI) (map[s
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1391,6 +1423,7 @@ func (c *InternalClient) GetPastQueries(ctx context.Context, uri *pnet.URI) ([]p
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request against the host.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1432,6 +1465,7 @@ func (c *InternalClient) FindIndexKeysNode(ctx context.Context, uri *pnet.URI, i
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Send the request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1480,6 +1514,7 @@ func (c *InternalClient) FindFieldKeysNode(ctx context.Context, uri *pnet.URI, i
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Send the request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1529,6 +1564,7 @@ func (c *InternalClient) CreateIndexKeysNode(ctx context.Context, uri *pnet.URI,
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Send the request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1581,6 +1617,7 @@ func (c *InternalClient) CreateFieldKeysNode(ctx context.Context, uri *pnet.URI,
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Send the request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1625,6 +1662,7 @@ func (c *InternalClient) MatchFieldKeysNode(ctx context.Context, uri *pnet.URI,
req.Header.Set("Content-Length", strconv.Itoa(len(like)))
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Send the request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -1664,6 +1702,7 @@ func (c *InternalClient) Transactions(ctx context.Context) (map[string]*pilosa.T
}
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx))
if err != nil {
@ -1702,6 +1741,7 @@ func (c *InternalClient) StartTransaction(ctx context.Context, id string, timeou
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx), giveRawResponse(true))
if err != nil {
@ -1736,6 +1776,7 @@ func (c *InternalClient) FinishTransaction(ctx context.Context, id string) (*pil
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx), giveRawResponse(true))
if err != nil {
@ -1772,6 +1813,7 @@ func (c *InternalClient) GetTransaction(ctx context.Context, id string) (*pilosa
}
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
resp, err := c.executeRequest(req.WithContext(ctx), giveRawResponse(true))
if err != nil {
@ -1797,6 +1839,8 @@ type executeOpts struct {
// giveRawResponse instructs executeRequest not to process the
// respStatusCode and try to extract errors or whatever.
giveRawResponse bool
// forwardAuthHeader instructs executeRequest not to follow redirects
forwardAuthHeader bool
}
type executeRequestOption func(*executeOpts)
@ -1806,6 +1850,11 @@ func giveRawResponse(b bool) executeRequestOption {
eo.giveRawResponse = b
}
}
func forwardAuthHeader(b bool) executeRequestOption {
return func(eo *executeOpts) {
eo.forwardAuthHeader = b
}
}
type nopCloser struct {
*bytes.Reader
@ -1831,7 +1880,25 @@ func (c *InternalClient) executeRetryableRequest(req *retryablehttp.Request, opt
opt(eo)
}
resp, err := c.retryableClient.Do(req)
var resp *http.Response
var err error
if eo.forwardAuthHeader {
rc := retryablehttp.NewClient()
rc.HTTPClient = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) > 0 {
req.Header.Set("Authorization", "Bearer "+getToken(via[0]))
}
return nil
},
}
rc.CheckRetry = retryWith400Policy
rc.Logger = logger.NopLogger
resp, err = rc.Do(req)
} else {
resp, err = c.retryableClient.Do(req)
}
return c.handleResponse(req.Request, eo, resp, err)
}
@ -1846,6 +1913,7 @@ func (c *InternalClient) handleResponse(req *http.Request, eo *executeOpts, resp
if eo.giveRawResponse {
return resp, nil
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
@ -2104,6 +2172,7 @@ func (c *InternalClient) RetrieveTranslatePartitionFromURI(ctx context.Context,
}
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -2171,6 +2240,11 @@ func (c *InternalClient) ImportFieldKeys(ctx context.Context, uri *pnet.URI, ind
}
httpReq.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
token, ok := ctx.Value("token").(string)
if ok && token != "" {
httpReq.Header.Set("Authorization", token)
}
// Execute request against the host.
resp, err := c.executeRetryableRequest(httpReq.WithContext(ctx))
if err != nil {
@ -2196,6 +2270,7 @@ func (c *InternalClient) ShardReader(ctx context.Context, index string, shard ui
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/octet-stream")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -2218,6 +2293,7 @@ func (c *InternalClient) IDAllocDataReader(ctx context.Context) (io.ReadCloser,
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/octet-stream")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -2227,6 +2303,30 @@ func (c *InternalClient) IDAllocDataReader(ctx context.Context) (io.ReadCloser,
return resp.Body, nil
}
func (c *InternalClient) IDAllocDataWriter(ctx context.Context, f io.Reader, primary *topology.Node) error {
span, ctx := tracing.StartSpanFromContext(ctx, "InternalClient.IDAllocDataWriter")
defer span.Finish()
u := primary.URI.Path("/internal/idalloc/restore")
// Build request.
req, err := http.NewRequest("POST", u, f)
if err != nil {
return errors.Wrap(err, "creating request")
}
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/octet-stream")
req = AddAuthToken(ctx, req)
// Execute request.
_, err = c.executeRequest(req.WithContext(ctx))
if err != nil {
return err
}
return err
}
// IndexTranslateDataReader returns a reader that provides a snapshot of
// translation data for a partition in an index.
func (c *InternalClient) IndexTranslateDataReader(ctx context.Context, index string, partitionID int) (io.ReadCloser, error) {
@ -2244,9 +2344,10 @@ func (c *InternalClient) IndexTranslateDataReader(ctx context.Context, index str
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/octet-stream")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
resp, err := c.executeRequest(req.WithContext(ctx), forwardAuthHeader(true))
if resp != nil && resp.StatusCode == http.StatusNotFound {
resp.Body.Close()
return nil, pilosa.ErrTranslateStoreNotFound
@ -2273,6 +2374,7 @@ func (c *InternalClient) FieldTranslateDataReader(ctx context.Context, index, fi
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/octet-stream")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -2303,6 +2405,7 @@ func (c *InternalClient) Status(ctx context.Context) (string, error) {
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))
@ -2334,6 +2437,7 @@ func (c *InternalClient) PartitionNodes(ctx context.Context, partitionID int) ([
req.Header.Set("User-Agent", "pilosa/"+pilosa.Version)
req.Header.Set("Accept", "application/json")
req = AddAuthToken(ctx, req)
// Execute request.
resp, err := c.executeRequest(req.WithContext(ctx))

View file

@ -443,8 +443,8 @@ func newRouter(handler *Handler) http.Handler {
// Truly used internally by featurebase
router.HandleFunc("/internal/cluster/message", handler.chkInternal(handler.handlePostClusterMessage)).Methods("POST").Name("PostClusterMessage")
router.HandleFunc("/internal/translate/data", handler.chkInternal(handler.handleGetTranslateData)).Methods("GET").Name("GetTranslateData")
router.HandleFunc("/internal/translate/data", handler.chkInternal(handler.handlePostTranslateData)).Methods("POST").Name("PostTranslateData")
router.HandleFunc("/internal/translate/data", handler.chkAuthZ(handler.handleGetTranslateData, authz.Read)).Methods("GET").Name("GetTranslateData")
router.HandleFunc("/internal/translate/data", handler.chkAuthZ(handler.handlePostTranslateData, authz.Write)).Methods("POST").Name("PostTranslateData")
// other ones
router.HandleFunc("/internal/fragment/block/data", handler.chkAuthN(handler.handleGetFragmentBlockData)).Methods("GET").Name("GetFragmentBlockData")
@ -566,7 +566,8 @@ func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc {
return
}
}
handler.ServeHTTP(w, r)
ctx := context.WithValue(r.Context(), "token", r.Header["Authorization"])
handler.ServeHTTP(w, r.WithContext(ctx))
}
}
@ -590,6 +591,7 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http
// put the user's groups in the context
ctx := context.WithValue(r.Context(), contextKeyGroupMembership, uinfo.Groups)
ctx = context.WithValue(ctx, "token", "Bearer "+uinfo.Token)
// unlikely h.permissions will be nil, but we'll check to be safe
if h.permissions == nil {

View file

@ -0,0 +1,77 @@
version: '2'
services:
pilosa1:
build:
context: ../..
dockerfile: Dockerfile-clustertests
image: ptest
environment:
- PILOSA_NAME=pilosa1
- PILOSA_ETCD_DIR=/root/.etcd
- PILOSA_ETCD_LISTEN_CLIENT_ADDRESS=http://0.0.0.0:10201
- PILOSA_ETCD_ADVERTISE_CLIENT_ADDRESS=http://pilosa1:10201
- PILOSA_ETCD_LISTEN_PEER_ADDRESS=http://0.0.0.0:10301
- PILOSA_ETCD_ADVERTISE_PEER_ADDRESS=http://pilosa1:10301
- PILOSA_ETCD_INITIAL_CLUSTER=pilosa1=http://pilosa1:10301,pilosa2=http://pilosa2:10301,pilosa3=http://pilosa3:10301
- PILOSA_CLUSTER_REPLICAS=3
networks:
- pilosanet
command:
- "/featurebase server --bind pilosa1:10101 -c /go/src/github.com/molecula/featurebase/internal/authclustertests/testdata/featurebase.conf"
pilosa2:
build:
context: ../..
dockerfile: Dockerfile-clustertests
image: ptest
environment:
- PILOSA_NAME=pilosa2
- PILOSA_ETCD_DIR=/root/.etcd
- PILOSA_ETCD_LISTEN_CLIENT_ADDRESS=http://0.0.0.0:10201
- PILOSA_ETCD_ADVERTISE_CLIENT_ADDRESS=http://pilosa2:10201
- PILOSA_ETCD_LISTEN_PEER_ADDRESS=http://0.0.0.0:10301
- PILOSA_ETCD_ADVERTISE_PEER_ADDRESS=http://pilosa2:10301
- PILOSA_ETCD_INITIAL_CLUSTER=pilosa1=http://pilosa1:10301,pilosa2=http://pilosa2:10301,pilosa3=http://pilosa3:10301
- PILOSA_CLUSTER_REPLICAS=3
networks:
- pilosanet
command:
- "/featurebase server --bind pilosa2:10101 -c /go/src/github.com/molecula/featurebase/internal/authclustertests/testdata/featurebase.conf"
pilosa3:
build:
context: ../..
dockerfile: Dockerfile-clustertests
image: ptest
environment:
- PILOSA_NAME=pilosa3
- PILOSA_ETCD_DIR=/root/.etcd
- PILOSA_ETCD_LISTEN_CLIENT_ADDRESS=http://0.0.0.0:10201
- PILOSA_ETCD_ADVERTISE_CLIENT_ADDRESS=http://pilosa3:10201
- PILOSA_ETCD_LISTEN_PEER_ADDRESS=http://0.0.0.0:10301
- PILOSA_ETCD_ADVERTISE_PEER_ADDRESS=http://pilosa3:10301
- PILOSA_ETCD_INITIAL_CLUSTER=pilosa1=http://pilosa1:10301,pilosa2=http://pilosa2:10301,pilosa3=http://pilosa3:10301
- PILOSA_CLUSTER_REPLICAS=3
networks:
- pilosanet
command:
- "/featurebase server --bind pilosa3:10101 -c /go/src/github.com/molecula/featurebase/internal/authclustertests/testdata/featurebase.conf"
client1:
build:
context: .
dockerfile: ../clustertests/Dockerfile
depends_on:
- "pilosa1"
- "pilosa2"
- "pilosa3"
environment:
- ENABLE_PILOSA_CLUSTER_TESTS=1
- GO111MODULE=on
- PROJECT=authclustertests
- ENABLE_AUTH=1
networks:
- pilosanet
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command:
- "cd /go/src/github.com/molecula/featurebase/ && go test -mod=vendor -v -count=1 github.com/molecula/featurebase/v3/internal/clustertests"
networks:
pilosanet:

View file

@ -0,0 +1,12 @@
# these test certs were generated with the following commands
certstrap --depot-path certs init --common-name pilosa-ca --expires "100 years"
certstrap --depot-path certs request-cert --common-name localhost --domain localhost
certstrap --depot-path certs sign "localhost" --CA pilosa-ca --expires "100 years"
# certstrap version
dev-25ea708a
(built with go 1.13)

View file

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEPjCCAiagAwIBAgIRAJ7rl74WPv8pLuhVRXt6fV0wDQYJKoZIhvcNAQELBQAw
FDESMBAGA1UEAxMJcGlsb3NhLWNhMCAXDTIwMTAyMDE5MTMzNFoYDzIxMjAxMDIw
MTkxMzE5WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDmi8FMWt23M0Cr2aCgEXUGQ0gv/4M7CXH/5GkSI866YwGV
Bd1iZMBRiONQwvGDnqYZRrAQv6mFjfyBqxdkbh++74FC3JK7sLhks0vg5VwbHV7T
5kj3bJqd+LKn5qPPOQXX9sgmv/NkggF/XXwF73noLPmgDQ78S+OP0ANmi1TQiU3a
gE+qp+Qpl5KC7dH9aC9nvE9iGfEcGNr+rXj05liiXqe4ZtIKWjeke7Ej64C6qX97
bNPzmLARtqbRsIkfAU8SJy3YuHfW8n1xr4B7ENm9jHQCh1wUv2YhaPpnio+/R2zp
Lw4yCqilDX9ZZ4nG3cBFuziSf+BUXJ9ydbw1aX8HAgMBAAGjgYgwgYUwDgYDVR0P
AQH/BAQDAgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4E
FgQUJRQJpaR5bp4ZyUsMxgK+UJl6PSgwHwYDVR0jBBgwFoAU69lmXSa5BZeyYU/6
XpWdtr59H1YwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IC
AQChxsBZ/b14ukJXX48BxAyZcy5r7GrLcRGQ3guUTONFVDWPzzpd8mjHi0yJDhMW
2zWtw3/H+c+zT7uRd+2sUxFdpAurNSFCdV++5Q/0aFvl+By5+MhVhtznEQDU0/lM
zFxiEYe/N9Vi2N0S1KPxvYL/RfBU27u+O/50zhjueM1BTyHTTqL6E2DFeT2VPKIg
zCDUtiTEDFZrD0XGITT/3CIoNCK8aC+Fq65OEoyEn6qR5qg1Kc4tfZmo6hWYiSlR
XeP36cP9R8kEMte1BdE74GVqE9cTuVZERdgB0hv3EME7Byq7uIm/a+JXbsh2/OFm
HcE0/HP+O0YK8YaVMGwI3pZYy2syWqPcakcvusETehr6P+Ihh2cOKRwqkCl6b87e
uSLJNTUMKZgakW6Bjv6lgQaWqnKzTC/RgmQ+G3w0nKATX9+jYE2j3MzZhbtcml+2
gp6u225yAJaYt/MQidwUMiKYeCgjaUNoL0fOJesGkokPk80ceISnqvbSRiZRTvK1
bVenkhkBrHuvvgKVstzcuZI9oQ2snWhK1naVQiOtQNEFUCHwyU95zADOK0km88NB
2het6yYaEUL9csHPEjPd3lFglerGQnil2Ly1slUC4jb7hfVRHjOFs8PVr9gQ45dW
Jvsv4pawHKFE0ennoNvoDmzbiY1TY5ScTZquPGIsEBV+tQ==
-----END CERTIFICATE-----

View file

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICgDCCAWgCAQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA5ovBTFrdtzNAq9mgoBF1BkNIL/+DOwlx/+RpEiPO
umMBlQXdYmTAUYjjUMLxg56mGUawEL+phY38gasXZG4fvu+BQtySu7C4ZLNL4OVc
Gx1e0+ZI92yanfiyp+ajzzkF1/bIJr/zZIIBf118Be956Cz5oA0O/Evjj9ADZotU
0IlN2oBPqqfkKZeSgu3R/WgvZ7xPYhnxHBja/q149OZYol6nuGbSClo3pHuxI+uA
uql/e2zT85iwEbam0bCJHwFPEict2Lh31vJ9ca+AexDZvYx0AodcFL9mIWj6Z4qP
v0ds6S8OMgqopQ1/WWeJxt3ARbs4kn/gVFyfcnW8NWl/BwIDAQABoCcwJQYJKoZI
hvcNAQkOMRgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQAD
ggEBABMi2/4j1/qzwWAYlEs2KW3z+apzzDLKgjE0kY6QvELh/8aBj0rMglb0HM2x
4iSSoX1ZwZgDZ9fIJ3klG/UF7CUweMghb9yC2PP9Z8WuqaECQyM87KgSln8PND9E
1OvD30rp9yr9KxEeckq+c1ebLi/qGrIY21VCwfxA0mv3sfi7Q5ONIckay/Xj+1Tz
ovE/TkM/8wTE/SKbpQSCkP7K1NDXuAhMGjcN0x3d3f8nBcLcZOrRroiHy38Bv/9T
Vd62IY6uqYw9sluBbMX72D/mmJiCKEw3+DhDJFhHCTCrAQM0QwLuwnG2lQFYoENc
ZAkwDIi+3DXHEEyloNSYGtXMEiA=
-----END CERTIFICATE REQUEST-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA5ovBTFrdtzNAq9mgoBF1BkNIL/+DOwlx/+RpEiPOumMBlQXd
YmTAUYjjUMLxg56mGUawEL+phY38gasXZG4fvu+BQtySu7C4ZLNL4OVcGx1e0+ZI
92yanfiyp+ajzzkF1/bIJr/zZIIBf118Be956Cz5oA0O/Evjj9ADZotU0IlN2oBP
qqfkKZeSgu3R/WgvZ7xPYhnxHBja/q149OZYol6nuGbSClo3pHuxI+uAuql/e2zT
85iwEbam0bCJHwFPEict2Lh31vJ9ca+AexDZvYx0AodcFL9mIWj6Z4qPv0ds6S8O
MgqopQ1/WWeJxt3ARbs4kn/gVFyfcnW8NWl/BwIDAQABAoIBAFX+GPqfBgY4cs3m
3ff2qvzMCdgFaXCS5Fe7XcmrW4fAOC3awynZRLbk5U0Reb5LZc8Vw8RriRLM1DuV
kqMeRG8WrNNArOafUxgUnJ/lTUa73MwTIHJRqxZzVkg0SjOYJGranOt/O4zoxSA5
wXIBUipc5Dtjw4wtzlKtFyefnuItL2MCdwOHUdZfnhr9Oykp1fuNqBqkkeryj3XV
ukHQvqU5zkMSayprNglziqTHUzU33iyZeDng+CJQeYTEc7Gn+zja2SFFBlPHqXXo
/OzAr94zI3vOnj3yRM3+sKMJVPV+RoJEGpsvPVuVn38d1VnIMEx8Gy/wif6tmM9c
7Q44hKECgYEA/JMFwkPGbry80ktDI065k5FIYn1EDRyUaQqyskmkBRcNW3qOShqj
o/zWQfCgxP587IEdKBBwqCpdqfghi3EW+JqfVlbGY6t1chAurYF/47CTIgKO5qRM
GdCY2OdiAeo5nba/KiLQfSuY08MCNDrQabLRJXIng8qVWpRwQzsv5rECgYEA6aw/
HugeQhTxk2uV91jJaAQIaxrt6JxuoG0CGGlbDrrTl2dnbPYA0muHMFdT/bzKjCpv
n/ScqbCyHuy+lWSnOzgedRNQCB46+0H58LAjITAj9QaT3raZqVReVIaD+pnx27dp
Cw5Ws6ENa9AQey3DO+dkRWot2AcLSw6TGR8HnzcCgYEA/ArfCU/G2cSgDJ6sPbSW
vaqR+C6W1Rq7AuN5FS8lbSrm2m2/RjW1LLTnPmAYntxx3zSs2sklErtMQovpNZRB
3w21iVwIl3eHOK7rVZtP+u++s4aoAYLcqjod/P1RMSYCHt85fpvFP9Ncq50DOwmh
5ohZ6ysyQXLMfdp4+K48i9ECgYEApOFALKu2ZgRnLRFV4REKFFX8Jq76vg5bVOF2
AAmfEbasBIIXDWBL1i2/V1HXVwv2k46B8wjj3ixqkr2UAM/j3DpN62g0KXZDQfUc
ykNOlmVkickZX6XSqRN5+ARubc5gRRuWiBGXBeqXEMLgTjpNLyCntP8l1++ofU6M
ZsZpV2MCgYA3nfNXAR5O4B/dm/2HmDQrXy0qia7Hwi/95pgL2FJaEmjBCPI1j12o
M5YCbhpr1pwsNKPV9AUlUz+OCwS8Vt+V0gQf9/XvNOsifU+mbMYVpuNGwmcKafnv
qECSeidrmhWJSR/SSNBcE94im/8ObVU110WJMkjC9otjDl9Aua/3LQ==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,16 @@
-----BEGIN X509 CRL-----
MIIChTBvAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCXBpbG9zYS1jYRcN
MjAxMDIwMTkxMzIyWhgPMjEyMDEwMjAxOTEzMjBaMACgIzAhMB8GA1UdIwQYMBaA
FOvZZl0muQWXsmFP+l6Vnba+fR9WMA0GCSqGSIb3DQEBCwUAA4ICAQBja+EDQAp+
KeD7UhWMMrTd9j03GgQ2E2Z7+Ba0qJ5+kS7/t+Yja2o5dQJkrC3GwEMOQb6DRRUE
nUE4xlr5Rryoq0dZk+Lp1f4cHrnP8l1xylUL44gsnY4v8zMR8L8X98vj7kKCqB8w
DFX7qkMlE5Ie2Hha7uuOJ85FnxIbMcRxFQH2m2zDfWG8/Lmxezvv9Hn45V/kwIQy
MmBh6cNuhzEneyNpM9yMRe/29QgVitF/2q6d+FzK8w8hkUFeYlyM+cP7F4Ml7160
UidSQM04zvBtJ8frZAvrDaPBBZhrTXcyw6+Qnp/aaW1ZsEIdHEcbYGNdgtazleoG
VH35cDP90KfiRbq69PQ9Zqn3cI//MX3sHrglA9wsEhHc9P7dowHaOFyxPouZPEmQ
/Jqg5oyJzujRwhf0v3SdJvhuDEzla2N+QyYRk0kRHtdv+glz7T7CnTYCk+DTv+oh
QABUrCbjfBoE5M2Qep9ZkIbl2gaDCpvbZSF4zFLKQc2aIOBpVn3HgTGBvdFD3FJY
Txl2F4Y3rS1T/WMAH86cZIc9h5HlMdFtAFnHAlHtB3wGw3FD/GcvGcvz2D4GaxKq
erzrnOxjYOA4M0haGzWF6dC7aPA8y35eZuqNXvbenTtc7A11bWTJfG1I7ctvLyPE
MpCNMHfymh/XtYZiZhvu6ueu3OeKScN+tA==
-----END X509 CRL-----

View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE6jCCAtKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlwaWxv
c2EtY2EwIBcNMjAxMDIwMTkxMzIyWhgPMjEyMDEwMjAxOTEzMjBaMBQxEjAQBgNV
BAMTCXBpbG9zYS1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALT/
uNmbnfXWNX+FsL0Waqw/5deti5F4cSjMrGRQpXxalTcooqNk/lkeqXkvi9ooFROZ
/HyQR9GM9dSD/aj6gD3FnGA4ueB24Xr6bWsRpDRh6+3UGLB3YCNNdGLSfX3LPMYh
RJutFmsg+r6SrSytbLbffu+0a/4fxtajZNwQJjDjd8qflXQZYlzp2LHk1A/jqqdI
fBtqkNg925TGKiavvUqKtdI/eFzRoiQ7NLBUJmszzveUXvOUMsMnW2/myLBe3Oqk
Vsy85lya0ADln20C3Lb0+ZA4KoGX3EWdtBXEuWqMoyvCJoJ4I3bH2LlfOUjRt8UE
pPk6sPMROJ+75mlgvgnSlYsN8PaZdvdm2VGVWRWUyEfyW/qa2fv8d2XBWqibl0YF
tqay9CX1aWgC9q12yx3vj7Yh+ZNbeZFLc7IL8zyNMwIjIOIIyGBY70KewfgVktzq
fAMz6h1sr9Kxozil97Cu3ma4B6UiL3rUbYMO/rNhVxcIuUoJpgIEVuRt+uXEG74y
XftauZ67qILFQzfpoacncvDEx5nJ3itLgbbyt1n1iWdGuEiMSLFT+x+nNUgVpQgA
sWRYxHdisM4xzRVN6pAaToMs1p8Ju7l9xU3z7RSogTyVk9gMIIV4t9TDYP5fI10Y
GFi7B6q0t3pIGXgKySHjCSl0EKYkDQDFl5tWeaiXAgMBAAGjRTBDMA4GA1UdDwEB
/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTr2WZdJrkFl7Jh
T/pelZ22vn0fVjANBgkqhkiG9w0BAQsFAAOCAgEAnlBFrWhB+WesCc3lhK980rA6
roNFYMZdaXvg4zaEGergkRvPab5yXoof1AAeznJm45GQfXn8HbQlrZmAqWg3fNld
/TX+jNvosM8K8K+PzesDGHsm/eQnbrb0qzMDsQgFY+nnD+x/ZQtjmKZtcNr/0ZlM
EJeXWU5cGy70GMbNztspMHsOLa3ZDLsBOJYOwSFxDlLDFrjZoRoPCWw8jRL+Tb4t
JjZcGZDD4a5+DqcojanIdNU1yI4teP6aV1LQTVNn4pwOap+tD0De/WzOPmXTQq5M
9ssxL7xSqVShQQMC8LVSWSRxtT6kLq0Av6i7wio0DZGnH3ynERTUs13DRZkwbVsE
OaQLmiQnsHRTIpdts/fswZ2FRPvdhhXxBjiGQZGEGXXznxHTNJ6nQioh95Ft5hNA
82i8Z74miaFIT/33/sZ5SuwUzphCgqCY2x7NUS8J313O9lsar0bweJTvQaZg/69E
PmmwUcDebh+pgKP01z4BqTzhtchmFUKzT+oOC8tmTeSlhsBTzO4xMw6OqhpXKL+c
k9f2CGUZYtEZHDRmP+C++FEi+B/tV2Oq3on+QPiaIIcRsOftthGUvJ8htUl3w+hq
B5TnL8CeLjXGKKRp+UiakrB4E7y2aIbrtIRnJ/Llg2XMND/0xbldsNsyDNXCIoDH
sz8HqwF3CUbv5XD4ioY=
-----END CERTIFICATE-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAtP+42Zud9dY1f4WwvRZqrD/l162LkXhxKMysZFClfFqVNyii
o2T+WR6peS+L2igVE5n8fJBH0Yz11IP9qPqAPcWcYDi54HbhevptaxGkNGHr7dQY
sHdgI010YtJ9fcs8xiFEm60WayD6vpKtLK1stt9+77Rr/h/G1qNk3BAmMON3yp+V
dBliXOnYseTUD+Oqp0h8G2qQ2D3blMYqJq+9Soq10j94XNGiJDs0sFQmazPO95Re
85Qywydbb+bIsF7c6qRWzLzmXJrQAOWfbQLctvT5kDgqgZfcRZ20FcS5aoyjK8Im
gngjdsfYuV85SNG3xQSk+Tqw8xE4n7vmaWC+CdKViw3w9pl292bZUZVZFZTIR/Jb
+prZ+/x3ZcFaqJuXRgW2prL0JfVpaAL2rXbLHe+PtiH5k1t5kUtzsgvzPI0zAiMg
4gjIYFjvQp7B+BWS3Op8AzPqHWyv0rGjOKX3sK7eZrgHpSIvetRtgw7+s2FXFwi5
SgmmAgRW5G365cQbvjJd+1q5nruogsVDN+mhpydy8MTHmcneK0uBtvK3WfWJZ0a4
SIxIsVP7H6c1SBWlCACxZFjEd2KwzjHNFU3qkBpOgyzWnwm7uX3FTfPtFKiBPJWT
2AwghXi31MNg/l8jXRgYWLsHqrS3ekgZeArJIeMJKXQQpiQNAMWXm1Z5qJcCAwEA
AQKCAgAWmjiDNCOtp2pW2mMPudToXbJeFJXxPJEk/yon/MotlUI8+R4WOW5pwqJ3
N7DHNWosYHZfN8VALdIlD7aFe4K4NA0rFupfVXki2lL/o9xVjkTgFjRfFQk0X1/B
V3fEVbTpKQ5gQmUiS6QEWFy3z5Bb5dz8IhO6UE2MUCswL/QU9tLmwrbvIJxf7fPZ
gzHYKh4NdcfJxK0B0/evxG9PFXMV8+xwrOxi6urMi3gw7NE/YeDemfChikgshqWs
e61kGPSNeKg+OPirZ8nB0urtugXF8yGXGOx18njXWLI8Zayh2Z4mwL/+WvJSyvIN
dA67QTUprULMvL+MGwJvMA+96Q7SBRVKR9HHNaP9pFsZup3QX9mqDQ9miB6+rzn7
f5RiSLVgq+HUPMPfqgXCkQZBcY28TcM1BZhS4uJJTkbvVTkrlHKJs6Q/LBqJFvq0
3+2M1xQb4HdRTRlwZ/YsxdqXGIoA3Xx3nZbb6LlPp/MT93xxsLJNNP47n445Cw8i
lz7hJJDwo+TyXmRRWKlFXO8TEhqKhK9ZEXmkXBxSeCQV0oTYS5kU9XdZ5iu5/CQQ
Lv+uFQfHTPWm/Lp5RC8JEEJwK5bwRAs5d9oWg5EbWJp2ol36g3YO1np5RFsQn3jL
qJPz35X3Bp9zQeZcAZpt1fWFdyX9f7V2LLCY8gUyIlfCEXBWwQKCAQEA8Uoes4ph
15tM1LswmOLqOq9iWuvcKYSNcz8my8nlP5zkUdGKvGcLblm5Mg4wKoPFiyjKhX7I
S8DUN8x7E5aiNBiZ0PGku4CKjubQgdFG/rYRfnrUEaA81Uw3a69eYioKbxhhxDzb
Lqd0/tGNHQZBQEyofOChwqTWpAdIh79F1oXejcPzRDr+oXxJVnvJDdYCEN/0TYkz
qeJdEtnVf1x2oNjuPIRuZNldpSDmiUce4QXG/qKgJcQNZ+paDDFK/G/rXHcIvV5C
du9yxxfppY7fRRMm+LFqDhKEWveG4OUhUgut71J0EREO65oMbf6UcZ2FS4XDTbFD
RSO4d8bKgf2eKwKCAQEAwAigvp8qq/yYitZeXhI6cI4ztSvwqblREhUgYWyrbUFo
a38Bey1fKQzJYUYA7raFU6alRHoHBQywjANhIaLlfvLtQfuZ4Z9CGP3Qn33uQR/E
ha4MNjjwUB0jx9lsDze1h61V95fxQLGNLVwGoaES4BpDRqvYJKnQv2X0SUigEg5U
GwryNlEW0AS/Xp/k7+PGJQernHIEWYS70FleHbAiINh+lzSfbJObgd6XnQ8IxtTr
xthXBKkkNBJdJX+/3qUQgOxTjSNUY4N9Np7myFfMvcAXuR7/K7bDegwHxffYb6Gc
v3fCFoTQFn1KTh0IvRjyv3WzqInAYVjC8CpD562VRQKCAQBirI4LnE7Q7lioMnj4
POvO3gRZ7FSXwfZap/vEoScYMaAJeajDzVwWX6jluHmoGUVC2IahuyxMFmpy+zNl
2lcw+NKGaRuV9kYzlF62iBABgBF9aNuq7Z2TGN0dM5VkjY7AyfbJWp3D4YVt4+JS
eUlb8z1//BkK0YBZigT2RplX1l0iGn00bO/OuFYBgRPCjb9AiWWOA8rV8ZVgbSbr
M7PrqWsb4oiGw4GRUvgUMbqGCWfMoFLfvuJAmc0DaXEh9N8KbD9tuctyeg+1LalG
JDxYMjHgyCT35kisLsfA1tMei1oxIcYHaLNyVAg7Pz4TjHiDXwt0jUZWUvpQOUJ9
kGsLAoIBAQCOqFoyAjBDIB16VpI4NDZx01IabxAUJfVSB5vMhFw9h++4m9tP1H7z
Eeqwdr7Ol40ofY4c9sIsQCcPfJs1z7vJuVIESJMih5sk0bmgIn9SpfTqkkfEKDxu
Z5djKeQa0fnrVxucGaZBtyT343uRqwVIsnn0EEk7w2OuLGFz553yi+5zQIh7TXYz
BrPb6dC7XWyfqbkVOaZ9khusRhei2mwgFnTEg3VDxcwqiF/9b2PHwfl9+M18SuL4
RAQqjWLOVbWS8P2Ixgw0+UOVxioP/xm8hO2auqo5oUZKbpF/wgVpuJenraHj9LpZ
Wq5OpUcOo3ACR8A1nk/qgXQf0mYrwEo5AoIBAHEqA2eJVZnPiAs6U7QPAavnLxt/
v0GLzsBBixSV8ErMToN1wfYtBb1t5fgF0Fuy85dREp1CsGJMgrnPX5bCnBmDaLl2
Z1lUaSDcFCu+yXo+Kuy7JvSKZ4++q4ggrHvK8y8FdKH4H+56vTdXe2i9RY/v48g4
kKyNiYtVXxrd/h47WbHF5eApheblH9hH6zC5tB/rW7Hh0nmnDcfmMW4BggbyBinH
MF3jO0YaspZOtRc2xSj8E3sGtN+f/KrBbKBb4J0j7VzuFmZC1u5grl/hx0cYE2ek
HGifmIjkKv5R4xPELoAJZyFOpN1PfS3Y+SOn0mF+RJRoGqMGcQWA3I77b5M=
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,383 @@
# FEATUREBASE HOST CONFIGURATION
#
# Uncomment when/where appropriate
# ==============================================================================
# Use advertise to specify the address advertised by the server to other nodes
# in the cluster and to clients via /status endpoint. Host defaults to IP
# address represented by bind parameter with network port.
#
# advertise = <IP Address>:10101
# advertise-grpc = <IP Address>:20101
# "long-query-time" represents duration of time that will trigger log and stat
# message for queries longer than X time. Ex. "1m30s" 1 minute 30 seconds
#
# long-query-time = "10s"
# Unique name for node in cluster. This is just a human-readable label for
# convenience and not used by any underlying logic.
# name = "pilosa1"
# # Host:Port where Featurebase server listens for HTTP requests.
# # Default is localhost:10101
# #
# bind = "pilosa1:10101"
# # The address and port featurebase will listen to for all GRPC connections
# # Ex. python-molecula, grafana for queries, etc.
# #
# bind-grpc = "localhost:20101"
# Directory to store Featurebase data files
# data-dir = "/var/lib/molecula"
# ==============================================================================
# CORS (Cross-Origin Resource Sharing) Allowed Origins
# List of allowed origin URIs for CORS
#
# [handler]
# allowed-origins = ["https://myapp.com", "https://myapp.org"]
# Path to the log file
# log-path = "/var/log/molecula/featurebase.log"
# Verbose - Enable verbose logging. Valid options are true or false.
# Set to true only when debugging as directed by Molecula engineers.
#
# verbose = true
# Soft limit on max number of files featurebase will keep open simultaneously.
# When past this limit, featurebase will only keep files open for as long as is
# needed to write updates.
#
# max-file-count = 900000
# Maximum number of active memory maps featurebase will use for fragment files.
# Actual total usage may be slightly higher.
# Best practice is to set this to ~10% lower than your system's max map count.
# See sysctl vm.max_map_count in Linux.
#
# max-map-count = 900000
# Max Writes Per Request - Max number of mutating commands allowed per request.
# This includes Set, Clear, ClearRow, and Store
#
# max-writes-per-request = 5000
# The following option sets the maximum number of queries that are maintained
# for the /query-history endpoint.
# This parameter is per-node, and the result combines the history from all nodes.
#
# query-history-length = 100
# External database to connect to for `ExternalLookup` queries.
# lookup-db-dsn = "postgres://localhost:5432/db"
# ==============================================================================
# For cluster stanza, "name" represents name for cluster. Must be same on all
# nodes in cluster. "replicas" represents number of hosts each piece of data
# should be stored on. Must be greater than or equal to 1 & less than or equal
# to number of nodes in cluster.
# [cluster]
# name = "cluster1"
# replicas = 1
# ==============================================================================
# [etcd]
# etcd is the tool Featurebase uses for node-to-node, intra-cluster
# communication. etcd is embedded in the featurebase cluster rather than
# running as a separate instance.
# It's important to configure this correctly for your network and nodes, and
# that it is consistent across all nodes.
#
# The easiest setup can be used when all nodes can reach all other nodes via a
# local subnet:
# listen-peer-address = advertise-peer-address
# = (what's in the initial-cluster-list)
# = the nodes ip address (which can be reached by every
# other node
# (localhost:10401 would not work for this, as each node can't reach that)
#
# If each node is separated by a proxy, or must be reached via url / dns, you
# will need to use a more complicated setup:
# listen-peer-address = the nodes local ip address
# (specific ip, localhost, or 0.0.0.0 for all
# local ip's)
# advertise-peer-address = the nodes ip address, reachable by all other nodes
# (This address should also be included in
# initital-cluster-list)
# in this case, you specify a different url/ip for listen-peer and
# advertise-peer. E.g. you specify 0.0.0.0 for listen, or (like in their case)
# you use a url for advertise. In each of these cases, you should set listen
# to the local ip, and you set advertise = to how each other node connects to
# this node, and you also use this same address in the initial cluster.
# The key here is that initial-cluster has to include the same node name and
# advertise-peer address as the node it's on (edited)
# for additional assistance, and for help with config issues,
# see https://etcd.io/docs/v3.5/faq/ cluster-url - URL of existing cluster
# that a new node should join when adding nodes to cluster.
#
# cluster-url = "http://localhost:10401"
# Address and port to bind to for client communication
# listen-client-address = "http://localhost:10401"
# Address and port to bind to for peer communication
# listen-peer-address = "http://localhost:10301"
# Comma-separated list of node=address pairs that makes up initial cluster when
# first started. In each pair, "node" value (left side of = ) should match
# name of node specified by "name" configuration parameter
#
# initial-cluster = "featurebase1=http://localhost:10301"
# ==============================================================================
# Profile Block Rate - Block Rate is passed directly to Go's
# runtime.SetBlockProfileRate. Goroutine blocking events will be sampled at 1
# per rate nanoseconds. A value of "1" samples every event, and 0 disables
# profiling.
#
# block-rate = 10000000
# Profile Mutex Fraction - Mutex Fraction is passed directly to Go's
# runtime.SetMutexProfileFraction. 1/ fraction of events will be sampled.
#
# mutex-fraction = 100
# ==============================================================================
# PostgreSQL Section
# [postgres]
#
# Endpoint Bind - Address to bind a PostgreSQL wire protocol endpoint.
# No PostgreSQL endpoint will be exposed unless a bind address is specified.
# Requires Molecula v3.0 or newer.
#
# bind = "localhost:55432"
# The PostgreSQL endpoint has support for a connection limit.
# This is generally not necessary, so it is disabled by default.
#
# connection-limit = 10000
# PostgreSQL Max Startup Packet Size - By default, the postgres endpoint
# uses an 8 MiB limit on incoming PostgreSQL startup packets. This should
# typically be sufficient, but may be exceeded if a client sends an unusually
# large amount of configuration data. Oversized startup packets are typically
# caused by connecting with a different protocol, e.g. HTTP.
#
# max-startup-size = 10000000
# PostgreSQL Timeouts
# In order to detect stalled clients, the PostgreSQL endpoint has connection
# read and write timeouts. There is also a startup timeout, which is used for
# connection setup. The read timeout does not impact idle connections. Idle
# connections will only be closed by the server if TCP keepalive reports a
# break in the connection. TCP keepalives use the default configuration
# provided by the host.
# Caution: Due to a limitation of the PostgreSQL wire protocol,
# raising the write timeout may delay the shutdown of a featurebase node.
#
# startup-timeout = "20s"
# read-timeout = "20s"
# write-timeout - "20s"
# Postgres Endpoint TLS - TLS configuration for the PostgreSQL endpoint is
# structured the same as the TLS configuration for Featurebase's other endpoints,
# but placed under [postgres.tls]. If TLS is configured on the postgres endpoint,
# Featurebase will reject unsecured connections.
# [postgres.tls]
# certificate = "/Users/souhailanoor/tls/out/auth.mybusiness.com.crt"
# key = "/Users/souhailanoor/tls/out/auth.mybusiness.com.key"
# ca-certificate = "/Users/souhailanoor/tls/out/auth.mybusiness.com.crt"
# enable-client-verification = true
# ==============================================================================
# Usage Duty Cycle - Featurebase maintains a disk/memory usage cache that is
# calculated periodically in the background and accessed by the UI/usage
# endpoint. Since this disk scan can take a long and unpredictable amount of
# time, its timing behavior is specified in a relative, rather than absolute
# sense. That is, the duty cycle sets the percentage of time that is spent
# recalculating this cache. This setting affects the results received from
# the "/ui/usage" http endpoint, as well as all data file and memory usage
# values and graphs on the webui "tables" page
# Special considerations:
# * If disk usage can be calculated quickly (less than 5 seconds), fresh
# results will be calculated when accessed
# * When disk usage takes longer to calculate, there is a minimum of one
# hour wait between cache recalculations
# Setting this value to 0 will completely disable the calculation of disk usage
#
# usage-duty-cycle = 20
# ==============================================================================
# Use [metric] stanza to define attributes for monitoring.
# [metric]
# Specify which service to use for collecting metrics. Valid options are:
# "statsd", "expvar", "prometheus", "none"
#
# service = "prometheus"
# Remote host to send statsd metrics to.
# host = "localhost:8125"
# The interval to send statsd metrics.
# poll-interval = "10s"
# Debugging flag to enable to send diagnostic information to Featurebase
# developers.
#
# diagnostics = false
# ==============================================================================
# TLS Certificate Section - Path to TLC certificate used for service HTTPS.
# Suffix should contain .crt or .pem
[tls]
certificate = "/go/src/github.com/molecula/featurebase/internal/authclustertests/testdata/certs/localhost.crt"
key = "/go/src/github.com/molecula/featurebase/internal/authclustertests/testdata/certs/localhost.key"
# ==============================================================================
# Tracing Section
# [tracing]
#
# Jaeger sampler type. Valid options are: "const, "probabilistic", "ratelimiting",
# or "remote". Set to 'off' to disable tracing completely.
#
# sampler-type = "remote"
# Jaeger sampler parameter (number)
# sampler-param = 0.001
# Tracing Agent Host:Port
# agent-host-port = "localhost:6831"
# ==============================================================================
# Configuration for the RBF storage format.
# [rbf]
# Maximum size for each RBF database file.
# Allocates virtual memory but does not preallocate physical disk space.
# If you get into the range where you have 16000 shards on a single node
# (across all indexes), you will need to lower this in order to not run out of
# virtual address space.
#
# max-db-size = 4294967296
# Maximum size for each RBF WAL file.
# Allocates virtual memory but does not preallocate physical disk space.
# This is the same as max-db-size, but for the write-ahead log. If you set it
# smaller, set max-wal-checkpoint-size to 1/2 of this (we will likely
# condense these options in the future).
#
# max-wal-size = 4294967296
# Minimum WAL size before WAL pages can be copied to the main database file.
# min-wal-checkpoint-size = 1048576
# Maximum WAL size before transactions are halted to copy WAL pages to the
# main database file.
#
# max-wal-checkpoint-size = 2147483648
# ==============================================================================
# [storage]
# Sync all changes to the file system.
# Should not be changed in production systems unless you know what you are
# doing - Should always be on unless testing or possibly while performing a
# bulk import and you are not worried about data loss
#
# fsync = true
# ==============================================================================
# Enable/Disable AuthN/AuthZ for featurebase
# Can choose identity provider, pass authorize and user-info endpoints, and client id
[auth]
enable = true
client-id = "e9088663-eb08-41d7-8f65-efb5f54bbb71"
client-secret = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
authorize-url="https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize"
token-url="https://login.microsoftonline.com/organizations/oauth2/v2.0/token"
group-endpoint-url = "https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true"
logout-url = "https://login.microsoftonline.com/common/oauth2/v2.0/logout"
scopes = ["https://graph.microsoft.com/.default", "offline_access"]
secret-key = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
permissions = "/go/src/github.com/molecula/featurebase/internal/authclustertests/testdata/permissions.yaml"
query-log-path = "query-log-test.log"
redirect-base-url = "https://localhost:10101"

View file

@ -0,0 +1,4 @@
user-groups:
"group-id-test":
"test": "write"
admin: "group-id-test"

View file

@ -11,9 +11,12 @@ import (
"testing"
"time"
"github.com/golang-jwt/jwt"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/authn"
"github.com/molecula/featurebase/v3/disco"
picli "github.com/molecula/featurebase/v3/http"
"github.com/molecula/featurebase/v3/logger"
)
// container turns a docker-compose service name into a container name
@ -24,16 +27,66 @@ import (
// as well, but I think it is true in recent versions.
func container(svc string) string {
project := "clustertests"
if os.Getenv("ENABLE_AUTH") == "1" {
project = "authclustertests"
}
if p := os.Getenv("PROJECT"); p != "" {
project = p
}
return project + "_" + svc + "_1"
}
func GetAuthToken(t *testing.T) string {
t.Helper()
var (
ClientID = "e9088663-eb08-41d7-8f65-efb5f54bbb71"
ClientSecret = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
AuthorizeURL = "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/authorize"
TokenURL = "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/token"
GroupEndpointURL = "https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true"
LogoutURL = "https://login.microsoftonline.com/common/oauth2/v2.0/logout"
Scopes = []string{"https://graph.microsoft.com/.default", "offline_access"}
Key = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
)
a, err := authn.NewAuth(
logger.NewStandardLogger(os.Stdout),
"http://localhost:10101/",
Scopes,
AuthorizeURL,
TokenURL,
GroupEndpointURL,
LogoutURL,
ClientID,
ClientSecret,
Key,
)
// make a valid token
tkn := jwt.New(jwt.SigningMethodHS256)
claims := tkn.Claims.(jwt.MapClaims)
groupString, _ := authn.ToGob64([]authn.Group{{GroupID: "group-id-test", GroupName: "group-name-test"}})
claims["molecula-idp-groups"] = groupString
claims["oid"] = "42"
claims["name"] = "valid"
token, err := tkn.SignedString([]byte(a.SecretKey()))
if err != nil {
t.Fatal(err)
}
return token
}
func TestClusterStuff(t *testing.T) {
if os.Getenv("ENABLE_PILOSA_CLUSTER_TESTS") != "1" {
t.Skip("pilosa cluster tests are not enabled")
}
auth := false
if os.Getenv("ENABLE_AUTH") == "1" {
auth = true
}
cli1, err := picli.NewInternalClient("pilosa1:10101", picli.GetHTTPClient(nil))
if err != nil {
t.Fatalf("getting client: %v", err)
@ -46,11 +99,18 @@ func TestClusterStuff(t *testing.T) {
if err != nil {
t.Fatalf("getting client: %v", err)
}
ctx := context.Background()
token := ""
// generate auth token and add to context
if auth {
token = GetAuthToken(t)
ctx = context.WithValue(ctx, "token", "Bearer "+token)
}
if err := cli1.CreateIndex(context.Background(), "testidx", pilosa.IndexOptions{}); err != nil {
if err := cli1.CreateIndex(ctx, "testidx", pilosa.IndexOptions{}); err != nil {
t.Fatalf("creating index: %v", err)
}
if err := cli1.CreateFieldWithOptions(context.Background(), "testidx", "testf", pilosa.FieldOptions{CacheType: pilosa.CacheTypeRanked, CacheSize: 100}); err != nil {
if err := cli1.CreateFieldWithOptions(ctx, "testidx", "testf", pilosa.FieldOptions{CacheType: pilosa.CacheTypeRanked, CacheSize: 100}); err != nil {
t.Fatalf("creating field: %v", err)
}
@ -66,7 +126,7 @@ func TestClusterStuff(t *testing.T) {
req.ColumnIDs[i%10] = uint64((i/10)*pilosa.ShardWidth + i%10)
req.Shard = uint64(i / 10)
if i%10 == 9 {
err = cli1.Import(context.Background(), nil, req, &pilosa.ImportOptions{})
err = cli1.Import(ctx, nil, req, &pilosa.ImportOptions{})
if err != nil {
t.Fatalf("importing: %v", err)
}
@ -75,7 +135,7 @@ func TestClusterStuff(t *testing.T) {
// Check query results from each node.
for i, cli := range []*picli.InternalClient{cli1, cli2, cli3} {
r, err := cli.Query(context.Background(), "testidx", &pilosa.QueryRequest{Index: "testidx", Query: "Count(Row(testf=0))"})
r, err := cli.Query(ctx, "testidx", &pilosa.QueryRequest{Index: "testidx", Query: "Count(Row(testf=0))"})
if err != nil {
t.Fatalf("count querying pilosa%d: %v", i, err)
}
@ -97,12 +157,12 @@ func TestClusterStuff(t *testing.T) {
}
t.Log("done with pause, waiting for stability")
waitForStatus(t, cli1.Status, string(disco.ClusterStateNormal), 30, time.Second)
waitForStatus(t, cli1.Status, string(disco.ClusterStateNormal), 30, time.Second, ctx)
t.Log("done waiting for stability")
// Check query results from each node.
for i, cli := range []*picli.InternalClient{cli1, cli2, cli3} {
r, err := cli.Query(context.Background(), "testidx", &pilosa.QueryRequest{Index: "testidx", Query: "Count(Row(testf=0))"})
r, err := cli.Query(ctx, "testidx", &pilosa.QueryRequest{Index: "testidx", Query: "Count(Row(testf=0))"})
if err != nil {
t.Fatalf("count querying pilosa%d: %v", i, err)
}
@ -119,9 +179,17 @@ func TestClusterStuff(t *testing.T) {
}
var backupCmd *exec.Cmd
tmpdir := t.TempDir()
if backupCmd, err = startCmd(
"featurebase", "backup", "--host=pilosa1:10101", fmt.Sprintf("--output=%s", tmpdir+"/backuptest")); err != nil {
t.Fatalf("sending backup command: %v", err)
if auth {
if backupCmd, err = startCmd(
"featurebase", "backup", "--host=pilosa1:10101", fmt.Sprintf("--output=%s", tmpdir+"/backuptest"), "--auth-token", token); err != nil {
t.Fatalf("sending backup command: %v", err)
}
} else {
if backupCmd, err = startCmd(
"featurebase", "backup", "--host=pilosa1:10101", fmt.Sprintf("--output=%s", tmpdir+"/backuptest")); err != nil {
t.Fatalf("sending backup command: %v", err)
}
}
time.Sleep(time.Second * 5)
if err = sendCmd("docker", "start", container("pilosa1")); err != nil {
@ -133,7 +201,11 @@ func TestClusterStuff(t *testing.T) {
}
client := http.Client{}
if req, err := http.NewRequest(http.MethodDelete, "http://pilosa1:10101/index/testidx", nil); err != nil {
req, err := http.NewRequest(http.MethodDelete, "http://pilosa1:10101/index/testidx", nil)
if auth {
req.Header.Set("Authorization", "Bearer "+token)
}
if err != nil {
t.Fatalf("getting req: %v", err)
} else if resp, err := client.Do(req); err != nil {
t.Fatalf("doing request: %v", err)
@ -146,8 +218,14 @@ func TestClusterStuff(t *testing.T) {
}
var restoreCmd *exec.Cmd
if restoreCmd, err = startCmd("featurebase", "restore", "-s", tmpdir+"/backuptest", "--host", "pilosa1:10101"); err != nil {
t.Fatalf("starting restore: %v", err)
if auth {
if restoreCmd, err = startCmd("featurebase", "restore", "-s", tmpdir+"/backuptest", "--host", "pilosa1:10101", "--auth-token", token); err != nil {
t.Fatalf("starting restore: %v", err)
}
} else {
if restoreCmd, err = startCmd("featurebase", "restore", "-s", tmpdir+"/backuptest", "--host", "pilosa1:10101"); err != nil {
t.Fatalf("starting restore: %v", err)
}
}
time.Sleep(time.Millisecond * 50)
if err = sendCmd("docker", "stop", container("pilosa2")); err != nil {
@ -165,9 +243,16 @@ func TestClusterStuff(t *testing.T) {
// now do backup with all nodes down and too short a timeout
// so it fails. Has be to be all 3 because the cluster has
// replicas=3 and the backup command will retry on replicas.
if backupCmd, err = startCmd(
"featurebase", "backup", "--host=pilosa1:10101", fmt.Sprintf("--output=%s", tmpdir+"/backuptest2"), "--retry-period=200ms"); err != nil {
t.Fatalf("sending second backup command: %v", err)
if auth {
if backupCmd, err = startCmd(
"featurebase", "backup", "--host=pilosa1:10101", fmt.Sprintf("--output=%s", tmpdir+"/backuptest2"), "--retry-period=200ms", "--auth-token", token); err != nil {
t.Fatalf("sending second backup command: %v", err)
}
} else {
if backupCmd, err = startCmd(
"featurebase", "backup", "--host=pilosa1:10101", fmt.Sprintf("--output=%s", tmpdir+"/backuptest2"), "--retry-period=200ms"); err != nil {
t.Fatalf("sending second backup command: %v", err)
}
}
time.Sleep(time.Millisecond * 10) // want the backup to get started, then fail
if err = sendCmd("docker", "stop", container("pilosa1")); err != nil {
@ -198,11 +283,11 @@ func TestClusterStuff(t *testing.T) {
})
}
func waitForStatus(t *testing.T, stator func(context.Context) (string, error), status string, n int, sleep time.Duration) {
func waitForStatus(t *testing.T, stator func(context.Context) (string, error), status string, n int, sleep time.Duration, ctx context.Context) {
t.Helper()
for i := 0; i < n; i++ {
s, err := stator(context.TODO())
s, err := stator(ctx)
if err != nil {
t.Logf("Status (try %d/%d): %v (retrying in %s)", i, n, err, sleep.String())
} else {
@ -214,7 +299,7 @@ func waitForStatus(t *testing.T, stator func(context.Context) (string, error), s
time.Sleep(sleep)
}
s, err := stator(context.TODO())
s, err := stator(ctx)
if err != nil {
t.Fatalf("querying status: %v", err)
}

View file

@ -65,6 +65,7 @@ services:
- ENABLE_PILOSA_CLUSTER_TESTS=1
- GO111MODULE=on
- PROJECT=${PROJECT}
- ENABLE_AUTH=0
networks:
- pilosanet
volumes:

View file

@ -270,6 +270,12 @@ func TestPauseReplica(t *testing.T) {
if os.Getenv("ENABLE_PILOSA_CLUSTER_TESTS") != "1" {
t.Skip("pilosa cluster tests for replication when a replica is paused are not enabled")
}
auth := false
if os.Getenv("ENABLE_AUTH") == "1" {
auth = true
}
// configurations for test
nodeNames := []string{"pilosa1", "pilosa2", "pilosa3"}
nodeToPause := "pilosa3"
@ -289,12 +295,17 @@ func TestPauseReplica(t *testing.T) {
uri := uris[0]
ctx := context.Background()
if auth {
token := GetAuthToken(t)
ctx = context.WithValue(ctx, "token", "Bearer "+token)
}
ctx, cancel := context.WithCancel(ctx)
t.Log("start Client")
// first achieve normal cluster status
waitForStatus(t, cli.Status, string(disco.ClusterStateNormal), 30, 1*time.Second)
waitForStatus(t, cli.Status, string(disco.ClusterStateNormal), 30, 1*time.Second, ctx)
// create keyed index
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
@ -354,7 +365,7 @@ func TestPauseReplica(t *testing.T) {
t.Logf("successfully end insert: %v", len(ts))
// wait for cluster status to be non-normal
waitForStatus(t, cli.Status, string(disco.ClusterStateDegraded), 30, 1*time.Second)
waitForStatus(t, cli.Status, string(disco.ClusterStateDegraded), 30, 1*time.Second, ctx)
// wait for cluster status to get back to normal
t.Logf("unpause %s", nodeToPause)
@ -362,7 +373,7 @@ func TestPauseReplica(t *testing.T) {
if err != nil {
t.Fatalf("error on unpause node %s: %v", nodeToPause, err)
}
waitForStatus(t, cli.Status, string(disco.ClusterStateNormal), 30, 1*time.Second)
waitForStatus(t, cli.Status, string(disco.ClusterStateNormal), 30, 1*time.Second, ctx)
// set up directory to store keys
basePath := "."