Skip to content

Commit

Permalink
tls: per-listener cert validation
Browse files Browse the repository at this point in the history
For the -l arguments with 'notls:ip:port' we now have
'btls:ip:port' and 'mtls:ip:port'

'btls' disables peer certificate verify on this listener, if it was
enabled globally by `ssl_verify_peer`.
'mtls' enables peer certificate verify on this listener. This is the
same as setting ssl_verify_peer to 'require'
  • Loading branch information
dormando committed Aug 9, 2024
1 parent a4bc538 commit 05320c9
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 7 deletions.
32 changes: 27 additions & 5 deletions memcached.c
Original file line number Diff line number Diff line change
Expand Up @@ -3480,7 +3480,7 @@ static void maximize_sndbuf(const int sfd) {
static int server_socket(const char *interface,
int port,
enum network_transport transport,
FILE *portnumber_file, bool ssl_enabled,
FILE *portnumber_file, uint8_t ssl_enabled,
uint64_t conntag,
enum protocol bproto) {
int sfd;
Expand Down Expand Up @@ -3658,10 +3658,14 @@ static int server_socket(const char *interface,

static int server_sockets(int port, enum network_transport transport,
FILE *portnumber_file) {
bool ssl_enabled = false;
uint8_t ssl_enabled = MC_SSL_DISABLED;

const char *notls = "notls";
ssl_enabled = settings.ssl_enabled;
const char *btls = "btls";
const char *mtls = "mtls";
if (settings.ssl_enabled) {
ssl_enabled = MC_SSL_ENABLED_DEFAULT;
}

if (settings.inter == NULL) {
return server_socket(settings.inter, port, transport, portnumber_file, ssl_enabled, 0, settings.binding_protocol);
Expand All @@ -3682,16 +3686,34 @@ static int server_sockets(int port, enum network_transport transport,
p = strtok_r(NULL, ";,", &b)) {
uint64_t conntag = 0;
int the_port = port;
ssl_enabled = settings.ssl_enabled;
if (settings.ssl_enabled) {
ssl_enabled = MC_SSL_ENABLED_DEFAULT;
}
// "notls" option is valid only when memcached is run with SSL enabled.
if (strncmp(p, notls, strlen(notls)) == 0) {
if (!settings.ssl_enabled) {
fprintf(stderr, "'notls' option is valid only when SSL is enabled\n");
free(list);
return 1;
}
ssl_enabled = false;
ssl_enabled = MC_SSL_DISABLED;
p += strlen(notls) + 1;
} else if (strncmp(p, btls, strlen(btls)) == 0) {
if (!settings.ssl_enabled) {
fprintf(stderr, "'btls' option is valid only when SSL is enabled\n");
free(list);
return 1;
}
ssl_enabled = MC_SSL_ENABLED_NOPEER;
p += strlen(btls) + 1;
} else if (strncmp(p, mtls, strlen(mtls)) == 0) {
if (!settings.ssl_enabled) {
fprintf(stderr, "'otls' option is valid only when SSL is enabled\n");
free(list);
return 1;
}
ssl_enabled = MC_SSL_ENABLED_PEER;
p += strlen(mtls) + 1;
}

// Allow forcing the protocol of this listener.
Expand Down
2 changes: 1 addition & 1 deletion memcached.h
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ struct conn {
bool close_after_write; /** flush write then move to close connection */
bool rbuf_malloced; /** read buffer was malloc'ed for ascii mget, needs free() */
bool item_malloced; /** item for conn_nread state is a temporary malloc */
bool ssl_enabled;
uint8_t ssl_enabled;
#ifdef TLS
void *ssl;
char *ssl_wbuf;
Expand Down
13 changes: 13 additions & 0 deletions t/lib/MemcachedTest.pm
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,19 @@ sub new_sock {
}
}

# needed for a specific test
sub new_nocert_tls_sock {
my $self = shift;
if (MemcachedTest::enabled_tls_testing()) {
my $port = shift;
my $ssl_version = shift;
return eval qq{ IO::Socket::SSL->new(PeerAddr => "$self->{host}:$port",
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
SSL_version => '$ssl_version');
};
}
}

sub new_udp_sock {
my $self = shift;
return IO::Socket::INET->new(PeerAddr => '127.0.0.1',
Expand Down
11 changes: 10 additions & 1 deletion t/ssl_ports.t
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ if (!enabled_tls_testing()) {

my $tcp_port = free_port();
my $ssl_port = free_port();
my $mtls_port = free_port();

my $server = new_memcached("-l notls:127.0.0.1:$tcp_port,127.0.0.1:$ssl_port", $ssl_port);
my $server = new_memcached("-l notls:127.0.0.1:$tcp_port,127.0.0.1:$ssl_port,mtls:127.0.0.1:$mtls_port", $ssl_port);
my $sock = $server->sock;

# Make sure we can talk over SSL
Expand All @@ -28,4 +29,12 @@ is(scalar <$sock>, "STORED\r\n", "stored foo");
my $tcp_sock = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$tcp_port");
mem_get_is($tcp_sock, "foo:123", "foo set over SSL");

# verify mTLS failure
# not trying very hard but: if the above works and this doesn't, it's going to
# be the peer cert. If someone wants to tryhard you can try inspecting
# $SSL_ERROR and/or checking `watch connevents` stream
my $mtls_sock = $server->new_nocert_tls_sock($mtls_port, 'TLSv1_3');
print $mtls_sock "version\r\n";
is(scalar <$mtls_sock>, undef, "failed to connect without peer cert");

done_testing()
9 changes: 9 additions & 0 deletions tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ void *ssl_accept(conn *c, int sfd, bool *fail) {
return NULL;
}
SSL_set_fd(ssl, sfd);

if (c->ssl_enabled == MC_SSL_ENABLED_NOPEER) {
// Don't enforce peer certs for this socket.
SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
} else if (c->ssl_enabled == MC_SSL_ENABLED_PEER) {
// Force peer validation for this socket.
SSL_set_verify(ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
}

ERR_clear_error();
int ret = SSL_accept(ssl);
if (ret <= 0) {
Expand Down
5 changes: 5 additions & 0 deletions tls.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#ifndef TLS_H
#define TLS_H

#define MC_SSL_DISABLED 0
#define MC_SSL_ENABLED_DEFAULT 1
#define MC_SSL_ENABLED_NOPEER 2
#define MC_SSL_ENABLED_PEER 3

#ifdef TLS
void *ssl_accept(conn *c, int sfd, bool *fail);
int ssl_init(void);
Expand Down

0 comments on commit 05320c9

Please sign in to comment.