2021-04-18 15:41:13 -04:00
|
|
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
|
|
//
|
|
|
|
// This file is part of MinIO Object Storage stack
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2018-03-15 16:03:41 -04:00
|
|
|
|
|
|
|
package target
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2019-10-11 20:46:03 -04:00
|
|
|
"context"
|
2020-06-08 08:55:44 -04:00
|
|
|
"crypto/tls"
|
2018-03-15 16:03:41 -04:00
|
|
|
"encoding/json"
|
2018-07-18 14:22:29 -04:00
|
|
|
"errors"
|
2018-03-15 16:03:41 -04:00
|
|
|
"fmt"
|
2018-09-05 19:47:14 -04:00
|
|
|
"io"
|
2018-03-15 16:03:41 -04:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2019-07-05 05:51:41 -04:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2021-07-09 14:47:04 -04:00
|
|
|
"strings"
|
2021-12-01 03:38:32 -05:00
|
|
|
"syscall"
|
2020-04-28 16:57:56 -04:00
|
|
|
"time"
|
2018-03-15 16:03:41 -04:00
|
|
|
|
2021-06-01 17:59:40 -04:00
|
|
|
"github.com/minio/minio/internal/event"
|
2022-07-27 12:44:59 -04:00
|
|
|
"github.com/minio/minio/internal/logger"
|
2021-05-28 18:17:01 -04:00
|
|
|
"github.com/minio/pkg/certs"
|
2021-06-14 17:54:37 -04:00
|
|
|
xnet "github.com/minio/pkg/net"
|
2018-03-15 16:03:41 -04:00
|
|
|
)
|
|
|
|
|
2019-10-23 01:59:13 -04:00
|
|
|
// Webhook constants
|
|
|
|
const (
|
|
|
|
WebhookEndpoint = "endpoint"
|
|
|
|
WebhookAuthToken = "auth_token"
|
|
|
|
WebhookQueueDir = "queue_dir"
|
|
|
|
WebhookQueueLimit = "queue_limit"
|
2020-06-08 08:55:44 -04:00
|
|
|
WebhookClientCert = "client_cert"
|
|
|
|
WebhookClientKey = "client_key"
|
2019-10-23 01:59:13 -04:00
|
|
|
|
2019-12-04 18:32:37 -05:00
|
|
|
EnvWebhookEnable = "MINIO_NOTIFY_WEBHOOK_ENABLE"
|
2019-10-23 01:59:13 -04:00
|
|
|
EnvWebhookEndpoint = "MINIO_NOTIFY_WEBHOOK_ENDPOINT"
|
|
|
|
EnvWebhookAuthToken = "MINIO_NOTIFY_WEBHOOK_AUTH_TOKEN"
|
|
|
|
EnvWebhookQueueDir = "MINIO_NOTIFY_WEBHOOK_QUEUE_DIR"
|
|
|
|
EnvWebhookQueueLimit = "MINIO_NOTIFY_WEBHOOK_QUEUE_LIMIT"
|
2020-06-08 08:55:44 -04:00
|
|
|
EnvWebhookClientCert = "MINIO_NOTIFY_WEBHOOK_CLIENT_CERT"
|
|
|
|
EnvWebhookClientKey = "MINIO_NOTIFY_WEBHOOK_CLIENT_KEY"
|
2019-10-23 01:59:13 -04:00
|
|
|
)
|
|
|
|
|
2018-03-15 16:03:41 -04:00
|
|
|
// WebhookArgs - Webhook target arguments.
|
|
|
|
type WebhookArgs struct {
|
2019-12-12 09:53:50 -05:00
|
|
|
Enable bool `json:"enable"`
|
|
|
|
Endpoint xnet.URL `json:"endpoint"`
|
|
|
|
AuthToken string `json:"authToken"`
|
|
|
|
Transport *http.Transport `json:"-"`
|
|
|
|
QueueDir string `json:"queueDir"`
|
|
|
|
QueueLimit uint64 `json:"queueLimit"`
|
2020-06-08 08:55:44 -04:00
|
|
|
ClientCert string `json:"clientCert"`
|
|
|
|
ClientKey string `json:"clientKey"`
|
2018-03-15 16:03:41 -04:00
|
|
|
}
|
|
|
|
|
2018-07-18 14:22:29 -04:00
|
|
|
// Validate WebhookArgs fields
|
|
|
|
func (w WebhookArgs) Validate() error {
|
|
|
|
if !w.Enable {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if w.Endpoint.IsEmpty() {
|
|
|
|
return errors.New("endpoint empty")
|
|
|
|
}
|
2019-07-05 05:51:41 -04:00
|
|
|
if w.QueueDir != "" {
|
|
|
|
if !filepath.IsAbs(w.QueueDir) {
|
|
|
|
return errors.New("queueDir path should be absolute")
|
|
|
|
}
|
|
|
|
}
|
2020-06-08 08:55:44 -04:00
|
|
|
if w.ClientCert != "" && w.ClientKey == "" || w.ClientCert == "" && w.ClientKey != "" {
|
|
|
|
return errors.New("cert and key must be specified as a pair")
|
|
|
|
}
|
2018-07-18 14:22:29 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-15 16:03:41 -04:00
|
|
|
// WebhookTarget - Webhook target.
|
|
|
|
type WebhookTarget struct {
|
|
|
|
id event.TargetID
|
|
|
|
args WebhookArgs
|
|
|
|
httpClient *http.Client
|
2019-07-05 05:51:41 -04:00
|
|
|
store Store
|
2022-07-27 12:44:59 -04:00
|
|
|
loggerOnce logger.LogOnce
|
2018-03-15 16:03:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ID - returns target ID.
|
|
|
|
func (target WebhookTarget) ID() event.TargetID {
|
|
|
|
return target.id
|
|
|
|
}
|
|
|
|
|
2020-04-21 12:38:32 -04:00
|
|
|
// HasQueueStore - Checks if the queueStore has been configured for the target
|
|
|
|
func (target *WebhookTarget) HasQueueStore() bool {
|
|
|
|
return target.store != nil
|
|
|
|
}
|
|
|
|
|
2019-12-11 17:27:03 -05:00
|
|
|
// IsActive - Return true if target is up and active
|
|
|
|
func (target *WebhookTarget) IsActive() (bool, error) {
|
2020-04-28 16:57:56 -04:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
|
2020-09-08 17:22:04 -04:00
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodHead, target.args.Endpoint.String(), nil)
|
2020-04-28 16:57:56 -04:00
|
|
|
if err != nil {
|
2020-10-29 12:52:11 -04:00
|
|
|
if xnet.IsNetworkOrHostDown(err, false) {
|
2020-04-28 16:57:56 -04:00
|
|
|
return false, errNotConnected
|
|
|
|
}
|
|
|
|
return false, err
|
2019-07-05 05:51:41 -04:00
|
|
|
}
|
2022-03-09 13:48:56 -05:00
|
|
|
tokens := strings.Fields(target.args.AuthToken)
|
|
|
|
switch len(tokens) {
|
|
|
|
case 2:
|
|
|
|
req.Header.Set("Authorization", target.args.AuthToken)
|
|
|
|
case 1:
|
|
|
|
req.Header.Set("Authorization", "Bearer "+target.args.AuthToken)
|
|
|
|
}
|
2020-04-28 16:57:56 -04:00
|
|
|
|
2020-09-08 17:22:04 -04:00
|
|
|
resp, err := target.httpClient.Do(req)
|
2020-04-28 16:57:56 -04:00
|
|
|
if err != nil {
|
2022-03-09 13:48:56 -05:00
|
|
|
if xnet.IsNetworkOrHostDown(err, true) {
|
2019-12-11 17:27:03 -05:00
|
|
|
return false, errNotConnected
|
2019-07-05 05:51:41 -04:00
|
|
|
}
|
2020-04-28 16:57:56 -04:00
|
|
|
return false, err
|
2019-12-11 17:27:03 -05:00
|
|
|
}
|
2022-09-19 14:05:16 -04:00
|
|
|
io.Copy(io.Discard, resp.Body)
|
2020-04-28 16:57:56 -04:00
|
|
|
resp.Body.Close()
|
|
|
|
// No network failure i.e response from the target means its up
|
2019-12-11 17:27:03 -05:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2022-03-09 13:48:56 -05:00
|
|
|
// Save - saves the events to the store if queuestore is configured,
|
|
|
|
// which will be replayed when the webhook connection is active.
|
2019-12-11 17:27:03 -05:00
|
|
|
func (target *WebhookTarget) Save(eventData event.Event) error {
|
|
|
|
if target.store != nil {
|
|
|
|
return target.store.Put(eventData)
|
|
|
|
}
|
2020-04-28 16:57:56 -04:00
|
|
|
err := target.send(eventData)
|
2019-12-11 17:27:03 -05:00
|
|
|
if err != nil {
|
2020-10-29 12:52:11 -04:00
|
|
|
if xnet.IsNetworkOrHostDown(err, false) {
|
2020-04-28 16:57:56 -04:00
|
|
|
return errNotConnected
|
|
|
|
}
|
2019-07-05 05:51:41 -04:00
|
|
|
}
|
2020-04-28 16:57:56 -04:00
|
|
|
return err
|
2019-04-10 08:46:01 -04:00
|
|
|
}
|
|
|
|
|
2019-07-05 05:51:41 -04:00
|
|
|
// send - sends an event to the webhook.
|
2019-04-10 08:46:01 -04:00
|
|
|
func (target *WebhookTarget) send(eventData event.Event) error {
|
2018-03-15 16:03:41 -04:00
|
|
|
objectName, err := url.QueryUnescape(eventData.S3.Object.Key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
key := eventData.S3.Bucket.Name + "/" + objectName
|
|
|
|
|
2018-11-14 13:23:44 -05:00
|
|
|
data, err := json.Marshal(event.Log{EventName: eventData.EventName, Key: key, Records: []event.Event{eventData}})
|
2018-03-15 16:03:41 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", target.args.Endpoint.String(), bytes.NewReader(data))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-07-09 14:47:04 -04:00
|
|
|
// Verify if the authToken already contains
|
|
|
|
// <Key> <Token> like format, if this is
|
|
|
|
// already present we can blindly use the
|
|
|
|
// authToken as is instead of adding 'Bearer'
|
|
|
|
tokens := strings.Fields(target.args.AuthToken)
|
|
|
|
switch len(tokens) {
|
|
|
|
case 2:
|
|
|
|
req.Header.Set("Authorization", target.args.AuthToken)
|
|
|
|
case 1:
|
2019-10-23 01:59:13 -04:00
|
|
|
req.Header.Set("Authorization", "Bearer "+target.args.AuthToken)
|
|
|
|
}
|
|
|
|
|
2018-03-15 16:03:41 -04:00
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
resp, err := target.httpClient.Do(req)
|
|
|
|
if err != nil {
|
2020-01-21 05:46:58 -05:00
|
|
|
target.Close()
|
2018-03-15 16:03:41 -04:00
|
|
|
return err
|
|
|
|
}
|
2019-12-12 09:53:50 -05:00
|
|
|
defer resp.Body.Close()
|
2022-09-19 14:05:16 -04:00
|
|
|
io.Copy(io.Discard, resp.Body)
|
2018-03-15 16:03:41 -04:00
|
|
|
|
2018-04-10 20:45:54 -04:00
|
|
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
2020-01-21 05:46:58 -05:00
|
|
|
target.Close()
|
2018-03-15 16:03:41 -04:00
|
|
|
return fmt.Errorf("sending event failed with %v", resp.Status)
|
|
|
|
}
|
2018-04-10 20:45:54 -04:00
|
|
|
|
|
|
|
return nil
|
2018-03-15 16:03:41 -04:00
|
|
|
}
|
|
|
|
|
2019-07-05 05:51:41 -04:00
|
|
|
// Send - reads an event from store and sends it to webhook.
|
2019-04-10 08:46:01 -04:00
|
|
|
func (target *WebhookTarget) Send(eventKey string) error {
|
2019-07-05 05:51:41 -04:00
|
|
|
eventData, eErr := target.store.Get(eventKey)
|
|
|
|
if eErr != nil {
|
|
|
|
// The last event key in a successful batch will be sent in the channel atmost once by the replayEvents()
|
|
|
|
// Such events will not exist and would've been already been sent successfully.
|
|
|
|
if os.IsNotExist(eErr) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return eErr
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := target.send(eventData); err != nil {
|
2020-10-29 12:52:11 -04:00
|
|
|
if xnet.IsNetworkOrHostDown(err, false) {
|
2019-09-19 11:23:43 -04:00
|
|
|
return errNotConnected
|
|
|
|
}
|
2019-07-05 05:51:41 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the event from store.
|
|
|
|
return target.store.Del(eventKey)
|
2019-04-10 08:46:01 -04:00
|
|
|
}
|
|
|
|
|
2018-03-15 16:03:41 -04:00
|
|
|
// Close - does nothing and available for interface compatibility.
|
|
|
|
func (target *WebhookTarget) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewWebhookTarget - creates new Webhook target.
|
2022-07-27 12:44:59 -04:00
|
|
|
func NewWebhookTarget(ctx context.Context, id string, args WebhookArgs, loggerOnce logger.LogOnce, transport *http.Transport, test bool) (*WebhookTarget, error) {
|
2019-07-05 05:51:41 -04:00
|
|
|
var store Store
|
2020-04-14 14:19:25 -04:00
|
|
|
target := &WebhookTarget{
|
2020-06-08 08:55:44 -04:00
|
|
|
id: event.TargetID{ID: id, Name: "webhook"},
|
|
|
|
args: args,
|
2020-04-14 14:19:25 -04:00
|
|
|
loggerOnce: loggerOnce,
|
|
|
|
}
|
|
|
|
|
2020-06-08 08:55:44 -04:00
|
|
|
if target.args.ClientCert != "" && target.args.ClientKey != "" {
|
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
|
|
|
manager, err := certs.NewManager(ctx, target.args.ClientCert, target.args.ClientKey, tls.LoadX509KeyPair)
|
2020-06-08 08:55:44 -04:00
|
|
|
if err != nil {
|
|
|
|
return target, err
|
|
|
|
}
|
2021-12-01 03:38:32 -05:00
|
|
|
manager.ReloadOnSignal(syscall.SIGHUP) // allow reloads upon SIGHUP
|
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
|
|
|
transport.TLSClientConfig.GetClientCertificate = manager.GetClientCertificate
|
2020-06-08 08:55:44 -04:00
|
|
|
}
|
|
|
|
target.httpClient = &http.Client{Transport: transport}
|
|
|
|
|
2019-07-05 05:51:41 -04:00
|
|
|
if args.QueueDir != "" {
|
|
|
|
queueDir := filepath.Join(args.QueueDir, storePrefix+"-webhook-"+id)
|
|
|
|
store = NewQueueStore(queueDir, args.QueueLimit)
|
2019-10-23 01:59:13 -04:00
|
|
|
if err := store.Open(); err != nil {
|
2022-07-27 12:44:59 -04:00
|
|
|
target.loggerOnce(context.Background(), err, target.ID().String())
|
2020-04-14 14:19:25 -04:00
|
|
|
return target, err
|
2019-07-05 05:51:41 -04:00
|
|
|
}
|
2020-04-14 14:19:25 -04:00
|
|
|
target.store = store
|
2019-07-05 05:51:41 -04:00
|
|
|
}
|
|
|
|
|
2020-04-28 16:57:56 -04:00
|
|
|
_, err := target.IsActive()
|
|
|
|
if err != nil {
|
2020-04-14 14:19:25 -04:00
|
|
|
if target.store == nil || err != errNotConnected {
|
2022-07-27 12:44:59 -04:00
|
|
|
target.loggerOnce(ctx, err, target.ID().String())
|
2020-04-14 14:19:25 -04:00
|
|
|
return target, err
|
|
|
|
}
|
2018-03-15 16:03:41 -04:00
|
|
|
}
|
2019-07-05 05:51:41 -04:00
|
|
|
|
2020-01-09 09:15:44 -05:00
|
|
|
if target.store != nil && !test {
|
2019-07-05 05:51:41 -04:00
|
|
|
// Replays the events from the store.
|
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
|
|
|
eventKeyCh := replayEvents(target.store, ctx.Done(), target.loggerOnce, target.ID())
|
2019-07-05 05:51:41 -04:00
|
|
|
// Start replaying events from the store.
|
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
|
|
|
go sendEvents(target, eventKeyCh, ctx.Done(), target.loggerOnce)
|
2019-07-05 05:51:41 -04:00
|
|
|
}
|
|
|
|
|
2019-10-23 01:59:13 -04:00
|
|
|
return target, nil
|
2018-03-15 16:03:41 -04:00
|
|
|
}
|