Ruby: support for PSM security (#25330)

* support for PSM security, SSL fallback

* Ruby Server, support for PSM security, SSL fallback

* address review comments

* add more tests, address review comments

* add XdsChannelCredentials class for PSM security, ruby client

* XdsServerCredentials

* address review comments

* re-run tools/distrib/clang_format_code.sh

* address comments, add entries to grpc_class_init_test

* fix to pass end2end ci test

* re-run tools/distrib/clang_format_code.sh

* address comments
pull/25625/head
Hannah Shi 4 years ago committed by GitHub
parent 444e6e6d64
commit 0fc521067b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      src/ruby/end2end/grpc_class_init_client.rb
  2. 3
      src/ruby/end2end/grpc_class_init_test.rb
  3. 11
      src/ruby/ext/grpc/rb_channel.c
  4. 12
      src/ruby/ext/grpc/rb_channel_credentials.c
  5. 4
      src/ruby/ext/grpc/rb_channel_credentials.h
  6. 4
      src/ruby/ext/grpc/rb_grpc.c
  7. 14
      src/ruby/ext/grpc/rb_server.c
  8. 22
      src/ruby/ext/grpc/rb_server_credentials.c
  9. 4
      src/ruby/ext/grpc/rb_server_credentials.h
  10. 215
      src/ruby/ext/grpc/rb_xds_channel_credentials.c
  11. 35
      src/ruby/ext/grpc/rb_xds_channel_credentials.h
  12. 169
      src/ruby/ext/grpc/rb_xds_server_credentials.c
  13. 35
      src/ruby/ext/grpc/rb_xds_server_credentials.h
  14. 6
      src/ruby/lib/grpc/generic/client_stub.rb
  15. 32
      src/ruby/spec/channel_credentials_spec.rb
  16. 11
      src/ruby/spec/channel_spec.rb
  17. 28
      src/ruby/spec/client_auth_spec.rb
  18. 25
      src/ruby/spec/server_credentials_spec.rb
  19. 22
      src/ruby/spec/server_spec.rb

@ -80,6 +80,31 @@ def get_test_proc(grpc_class)
return proc do return proc do
GRPC::Core::ChannelCredentials.new GRPC::Core::ChannelCredentials.new
end end
when 'xds_channel_credentials'
return proc do
GRPC::Core::XdsChannelCredentials.new(GRPC::Core::ChannelCredentials.new)
end
when 'server_credentials'
return proc do
test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
files = ['ca.pem', 'server1.key', 'server1.pem']
creds = files.map { |f| File.open(File.join(test_root, f)).read }
GRPC::Core::ServerCredentials.new(
creds[0],
[{ private_key: creds[1], cert_chain: creds[2] }],
true)
end
when 'xds_server_credentials'
return proc do
test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
files = ['ca.pem', 'server1.key', 'server1.pem']
creds = files.map { |f| File.open(File.join(test_root, f)).read }
GRPC::Core::XdsServerCredentials.new(
GRPC::Core::ServerCredentials.new(
creds[0],
[{ private_key: creds[1], cert_chain: creds[2] }],
true))
end
when 'call_credentials' when 'call_credentials'
return proc do return proc do
GRPC::Core::CallCredentials.new(proc { |noop| noop }) GRPC::Core::CallCredentials.new(proc { |noop| noop })

@ -20,6 +20,9 @@ def main
native_grpc_classes = %w( channel native_grpc_classes = %w( channel
server server
channel_credentials channel_credentials
xds_channel_credentials
server_credentials
xds_server_credentials
call_credentials call_credentials
compression_options ) compression_options )

@ -34,6 +34,7 @@
#include "rb_completion_queue.h" #include "rb_completion_queue.h"
#include "rb_grpc.h" #include "rb_grpc.h"
#include "rb_server.h" #include "rb_server.h"
#include "rb_xds_channel_credentials.h"
/* id_channel is the name of the hidden ivar that preserves a reference to the /* id_channel is the name of the hidden ivar that preserves a reference to the
* channel on a call, so that calls are not GCed before their channel. */ * channel on a call, so that calls are not GCed before their channel. */
@ -242,7 +243,15 @@ static VALUE grpc_rb_channel_init(int argc, VALUE* argv, VALUE self) {
ch = grpc_insecure_channel_create(target_chars, &args, NULL); ch = grpc_insecure_channel_create(target_chars, &args, NULL);
} else { } else {
wrapper->credentials = credentials; wrapper->credentials = credentials;
creds = grpc_rb_get_wrapped_channel_credentials(credentials); if (grpc_rb_is_channel_credentials(credentials)) {
creds = grpc_rb_get_wrapped_channel_credentials(credentials);
} else if (grpc_rb_is_xds_channel_credentials(credentials)) {
creds = grpc_rb_get_wrapped_xds_channel_credentials(credentials);
} else {
rb_raise(rb_eTypeError,
"bad creds, want ChannelCredentials or XdsChannelCredentials");
return Qnil;
}
ch = grpc_secure_channel_create(creds, target_chars, &args, NULL); ch = grpc_secure_channel_create(creds, target_chars, &args, NULL);
} }

@ -180,7 +180,11 @@ static VALUE grpc_rb_channel_credentials_init(int argc, VALUE* argv,
NULL, NULL); NULL, NULL);
} }
if (creds == NULL) { if (creds == NULL) {
rb_raise(rb_eRuntimeError, "could not create a credentials, not sure why"); rb_raise(rb_eRuntimeError,
"the call to grpc_ssl_credentials_create() failed, could not "
"create a credentials, see "
"https://github.com/grpc/grpc/blob/master/TROUBLESHOOTING.md for "
"debugging tips");
return Qnil; return Qnil;
} }
wrapper->wrapped = creds; wrapper->wrapped = creds;
@ -270,7 +274,13 @@ void Init_grpc_channel_credentials() {
/* Gets the wrapped grpc_channel_credentials from the ruby wrapper */ /* Gets the wrapped grpc_channel_credentials from the ruby wrapper */
grpc_channel_credentials* grpc_rb_get_wrapped_channel_credentials(VALUE v) { grpc_channel_credentials* grpc_rb_get_wrapped_channel_credentials(VALUE v) {
grpc_rb_channel_credentials* wrapper = NULL; grpc_rb_channel_credentials* wrapper = NULL;
Check_TypedStruct(v, &grpc_rb_channel_credentials_data_type);
TypedData_Get_Struct(v, grpc_rb_channel_credentials, TypedData_Get_Struct(v, grpc_rb_channel_credentials,
&grpc_rb_channel_credentials_data_type, wrapper); &grpc_rb_channel_credentials_data_type, wrapper);
return wrapper->wrapped; return wrapper->wrapped;
} }
/* Check if v is kind of ChannelCredentials */
bool grpc_rb_is_channel_credentials(VALUE v) {
return rb_typeddata_is_kind_of(v, &grpc_rb_channel_credentials_data_type);
}

@ -20,6 +20,7 @@
#define GRPC_RB_CREDENTIALS_H_ #define GRPC_RB_CREDENTIALS_H_
#include <ruby/ruby.h> #include <ruby/ruby.h>
#include <stdbool.h>
#include <grpc/grpc_security.h> #include <grpc/grpc_security.h>
@ -29,4 +30,7 @@ void Init_grpc_channel_credentials();
/* Gets the wrapped credentials from the ruby wrapper */ /* Gets the wrapped credentials from the ruby wrapper */
grpc_channel_credentials* grpc_rb_get_wrapped_channel_credentials(VALUE v); grpc_channel_credentials* grpc_rb_get_wrapped_channel_credentials(VALUE v);
/* Check if v is kind of ChannelCredentials */
bool grpc_rb_is_channel_credentials(VALUE v);
#endif /* GRPC_RB_CREDENTIALS_H_ */ #endif /* GRPC_RB_CREDENTIALS_H_ */

@ -40,6 +40,8 @@
#include "rb_loader.h" #include "rb_loader.h"
#include "rb_server.h" #include "rb_server.h"
#include "rb_server_credentials.h" #include "rb_server_credentials.h"
#include "rb_xds_channel_credentials.h"
#include "rb_xds_server_credentials.h"
static VALUE grpc_rb_cTimeVal = Qnil; static VALUE grpc_rb_cTimeVal = Qnil;
@ -321,8 +323,10 @@ void Init_grpc_c() {
Init_grpc_call(); Init_grpc_call();
Init_grpc_call_credentials(); Init_grpc_call_credentials();
Init_grpc_channel_credentials(); Init_grpc_channel_credentials();
Init_grpc_xds_channel_credentials();
Init_grpc_server(); Init_grpc_server();
Init_grpc_server_credentials(); Init_grpc_server_credentials();
Init_grpc_xds_server_credentials();
Init_grpc_time_consts(); Init_grpc_time_consts();
Init_grpc_compression_options(); Init_grpc_compression_options();
} }

@ -31,6 +31,7 @@
#include "rb_completion_queue.h" #include "rb_completion_queue.h"
#include "rb_grpc.h" #include "rb_grpc.h"
#include "rb_server_credentials.h" #include "rb_server_credentials.h"
#include "rb_xds_server_credentials.h"
/* grpc_rb_cServer is the ruby class that proxies grpc_server. */ /* grpc_rb_cServer is the ruby class that proxies grpc_server. */
static VALUE grpc_rb_cServer = Qnil; static VALUE grpc_rb_cServer = Qnil;
@ -326,7 +327,18 @@ static VALUE grpc_rb_server_add_http2_port(VALUE self, VALUE port,
StringValueCStr(port)); StringValueCStr(port));
} }
} else { } else {
creds = grpc_rb_get_wrapped_server_credentials(rb_creds); // TODO: create a common parent class for all server-side credentials,
// then we can have a single method to retrieve the underlying
// grpc_server_credentials object, and avoid the need for this reflection
if (grpc_rb_is_server_credentials(rb_creds)) {
creds = grpc_rb_get_wrapped_server_credentials(rb_creds);
} else if (grpc_rb_is_xds_server_credentials(rb_creds)) {
creds = grpc_rb_get_wrapped_xds_server_credentials(rb_creds);
} else {
rb_raise(rb_eTypeError,
"failed to create server because credentials parameter has an "
"invalid type, want ServerCredentials or XdsServerCredentials");
}
recvd_port = grpc_server_add_secure_http2_port( recvd_port = grpc_server_add_secure_http2_port(
s->wrapped, StringValueCStr(port), creds); s->wrapped, StringValueCStr(port), creds);
if (recvd_port == 0) { if (recvd_port == 0) {

@ -42,7 +42,7 @@ typedef struct grpc_rb_server_credentials {
} grpc_rb_server_credentials; } grpc_rb_server_credentials;
/* Destroys the server credentials instances. */ /* Destroys the server credentials instances. */
static void grpc_rb_server_credentials_free(void* p) { static void grpc_rb_server_credentials_free_internal(void* p) {
grpc_rb_server_credentials* wrapper = NULL; grpc_rb_server_credentials* wrapper = NULL;
if (p == NULL) { if (p == NULL) {
return; return;
@ -59,6 +59,12 @@ static void grpc_rb_server_credentials_free(void* p) {
xfree(p); xfree(p);
} }
/* Destroys the server credentials instances. */
static void grpc_rb_server_credentials_free(void* p) {
grpc_rb_server_credentials_free_internal(p);
grpc_ruby_shutdown();
}
/* Protects the mark object from GC */ /* Protects the mark object from GC */
static void grpc_rb_server_credentials_mark(void* p) { static void grpc_rb_server_credentials_mark(void* p) {
grpc_rb_server_credentials* wrapper = NULL; grpc_rb_server_credentials* wrapper = NULL;
@ -87,9 +93,9 @@ static const rb_data_type_t grpc_rb_server_credentials_data_type = {
}; };
/* Allocates ServerCredential instances. /* Allocates ServerCredential instances.
Provides safe initial defaults for the instance fields. */ Provides safe initial defaults for the instance fields. */
static VALUE grpc_rb_server_credentials_alloc(VALUE cls) { static VALUE grpc_rb_server_credentials_alloc(VALUE cls) {
grpc_ruby_init();
grpc_rb_server_credentials* wrapper = ALLOC(grpc_rb_server_credentials); grpc_rb_server_credentials* wrapper = ALLOC(grpc_rb_server_credentials);
wrapper->wrapped = NULL; wrapper->wrapped = NULL;
wrapper->mark = Qnil; wrapper->mark = Qnil;
@ -202,7 +208,11 @@ static VALUE grpc_rb_server_credentials_init(VALUE self, VALUE pem_root_certs,
} }
xfree(key_cert_pairs); xfree(key_cert_pairs);
if (creds == NULL) { if (creds == NULL) {
rb_raise(rb_eRuntimeError, "could not create a credentials, not sure why"); rb_raise(rb_eRuntimeError,
"the call to grpc_ssl_server_credentials_create_ex() failed, "
"could not create a credentials, see "
"https://github.com/grpc/grpc/blob/master/TROUBLESHOOTING.md for "
"debugging tips");
return Qnil; return Qnil;
} }
wrapper->wrapped = creds; wrapper->wrapped = creds;
@ -237,7 +247,13 @@ void Init_grpc_server_credentials() {
/* Gets the wrapped grpc_server_credentials from the ruby wrapper */ /* Gets the wrapped grpc_server_credentials from the ruby wrapper */
grpc_server_credentials* grpc_rb_get_wrapped_server_credentials(VALUE v) { grpc_server_credentials* grpc_rb_get_wrapped_server_credentials(VALUE v) {
grpc_rb_server_credentials* wrapper = NULL; grpc_rb_server_credentials* wrapper = NULL;
Check_TypedStruct(v, &grpc_rb_server_credentials_data_type);
TypedData_Get_Struct(v, grpc_rb_server_credentials, TypedData_Get_Struct(v, grpc_rb_server_credentials,
&grpc_rb_server_credentials_data_type, wrapper); &grpc_rb_server_credentials_data_type, wrapper);
return wrapper->wrapped; return wrapper->wrapped;
} }
/* Check if v is kind of ServerCredentials */
bool grpc_rb_is_server_credentials(VALUE v) {
return rb_typeddata_is_kind_of(v, &grpc_rb_server_credentials_data_type);
}

@ -20,6 +20,7 @@
#define GRPC_RB_SERVER_CREDENTIALS_H_ #define GRPC_RB_SERVER_CREDENTIALS_H_
#include <ruby/ruby.h> #include <ruby/ruby.h>
#include <stdbool.h>
#include <grpc/grpc_security.h> #include <grpc/grpc_security.h>
@ -29,4 +30,7 @@ void Init_grpc_server_credentials();
/* Gets the wrapped server_credentials from the ruby wrapper */ /* Gets the wrapped server_credentials from the ruby wrapper */
grpc_server_credentials* grpc_rb_get_wrapped_server_credentials(VALUE v); grpc_server_credentials* grpc_rb_get_wrapped_server_credentials(VALUE v);
/* Check if v is kind of ServerCredentials */
bool grpc_rb_is_server_credentials(VALUE v);
#endif /* GRPC_RB_SERVER_CREDENTIALS_H_ */ #endif /* GRPC_RB_SERVER_CREDENTIALS_H_ */

@ -0,0 +1,215 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <ruby/ruby.h>
#include <string.h>
#include "rb_call_credentials.h"
#include "rb_channel_credentials.h"
#include "rb_grpc.h"
#include "rb_grpc_imports.generated.h"
#include "rb_xds_channel_credentials.h"
/* grpc_rb_cXdsChannelCredentials is the ruby class that proxies
grpc_channel_credentials. */
static VALUE grpc_rb_cXdsChannelCredentials = Qnil;
/* grpc_rb_xds_channel_credentials wraps a grpc_channel_credentials. It
* provides a mark object that is used to hold references to any objects used to
* create the credentials. */
typedef struct grpc_rb_xds_channel_credentials {
/* Holder of ruby objects involved in constructing the credentials */
VALUE mark;
/* The actual credentials */
grpc_channel_credentials* wrapped;
} grpc_rb_xds_channel_credentials;
static void grpc_rb_xds_channel_credentials_free_internal(void* p) {
grpc_rb_xds_channel_credentials* wrapper = NULL;
if (p == NULL) {
return;
};
wrapper = (grpc_rb_xds_channel_credentials*)p;
grpc_channel_credentials_release(wrapper->wrapped);
wrapper->wrapped = NULL;
xfree(p);
}
/* Destroys the credentials instances. */
static void grpc_rb_xds_channel_credentials_free(void* p) {
grpc_rb_xds_channel_credentials_free_internal(p);
grpc_ruby_shutdown();
}
/* Protects the mark object from GC */
static void grpc_rb_xds_channel_credentials_mark(void* p) {
grpc_rb_xds_channel_credentials* wrapper = NULL;
if (p == NULL) {
return;
}
wrapper = (grpc_rb_xds_channel_credentials*)p;
if (wrapper->mark != Qnil) {
rb_gc_mark(wrapper->mark);
}
}
static rb_data_type_t grpc_rb_xds_channel_credentials_data_type = {
"grpc_xds_channel_credentials",
{grpc_rb_xds_channel_credentials_mark, grpc_rb_xds_channel_credentials_free,
GRPC_RB_MEMSIZE_UNAVAILABLE, NULL},
NULL,
NULL,
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
RUBY_TYPED_FREE_IMMEDIATELY
#endif
};
/* Allocates ChannelCredential instances.
Provides safe initial defaults for the instance fields. */
static VALUE grpc_rb_xds_channel_credentials_alloc(VALUE cls) {
grpc_ruby_init();
grpc_rb_xds_channel_credentials* wrapper =
ALLOC(grpc_rb_xds_channel_credentials);
wrapper->wrapped = NULL;
wrapper->mark = Qnil;
return TypedData_Wrap_Struct(cls, &grpc_rb_xds_channel_credentials_data_type,
wrapper);
}
/* Creates a wrapping object for a given channel credentials. This should only
* be called with grpc_channel_credentials objects that are not already
* associated with any Ruby object. */
VALUE grpc_rb_xds_wrap_channel_credentials(grpc_channel_credentials* c,
VALUE mark) {
grpc_rb_xds_channel_credentials* wrapper;
if (c == NULL) {
return Qnil;
}
VALUE rb_wrapper =
grpc_rb_xds_channel_credentials_alloc(grpc_rb_cXdsChannelCredentials);
TypedData_Get_Struct(rb_wrapper, grpc_rb_xds_channel_credentials,
&grpc_rb_xds_channel_credentials_data_type, wrapper);
wrapper->wrapped = c;
wrapper->mark = mark;
return rb_wrapper;
}
/* The attribute used on the mark object to hold the fallback creds. */
static ID id_fallback_creds;
/*
call-seq:
fallback_creds: (ChannelCredentials) fallback credentials to create
XDS credentials
Initializes Credential instances. */
static VALUE grpc_rb_xds_channel_credentials_init(VALUE self,
VALUE fallback_creds) {
grpc_rb_xds_channel_credentials* wrapper = NULL;
grpc_channel_credentials* grpc_fallback_creds =
grpc_rb_get_wrapped_channel_credentials(fallback_creds);
grpc_channel_credentials* creds =
grpc_xds_credentials_create(grpc_fallback_creds);
if (creds == NULL) {
rb_raise(rb_eRuntimeError,
"the call to grpc_xds_credentials_create() failed, could not "
"create a credentials, , see "
"https://github.com/grpc/grpc/blob/master/TROUBLESHOOTING.md for "
"debugging tips");
return Qnil;
}
TypedData_Get_Struct(self, grpc_rb_xds_channel_credentials,
&grpc_rb_xds_channel_credentials_data_type, wrapper);
wrapper->wrapped = creds;
/* Add the input objects as hidden fields to preserve them. */
rb_ivar_set(self, id_fallback_creds, fallback_creds);
return self;
}
// TODO: de-duplicate this code with the similar method in
// rb_channel_credentials.c, after putting ChannelCredentials and
// XdsChannelCredentials under a common parent class
static VALUE grpc_rb_xds_channel_credentials_compose(int argc, VALUE* argv,
VALUE self) {
grpc_channel_credentials* creds;
grpc_call_credentials* other;
grpc_channel_credentials* prev = NULL;
VALUE mark;
if (argc == 0) {
return self;
}
mark = rb_ary_new();
rb_ary_push(mark, self);
creds = grpc_rb_get_wrapped_xds_channel_credentials(self);
for (int i = 0; i < argc; i++) {
rb_ary_push(mark, argv[i]);
other = grpc_rb_get_wrapped_call_credentials(argv[i]);
creds = grpc_composite_channel_credentials_create(creds, other, NULL);
if (prev != NULL) {
grpc_channel_credentials_release(prev);
}
prev = creds;
if (creds == NULL) {
rb_raise(rb_eRuntimeError,
"Failed to compose channel and call credentials");
}
}
return grpc_rb_xds_wrap_channel_credentials(creds, mark);
}
void Init_grpc_xds_channel_credentials() {
grpc_rb_cXdsChannelCredentials = rb_define_class_under(
grpc_rb_mGrpcCore, "XdsChannelCredentials", rb_cObject);
/* Allocates an object managed by the ruby runtime */
rb_define_alloc_func(grpc_rb_cXdsChannelCredentials,
grpc_rb_xds_channel_credentials_alloc);
/* Provides a ruby constructor and support for dup/clone. */
rb_define_method(grpc_rb_cXdsChannelCredentials, "initialize",
grpc_rb_xds_channel_credentials_init, 1);
rb_define_method(grpc_rb_cXdsChannelCredentials, "initialize_copy",
grpc_rb_cannot_init_copy, 1);
rb_define_method(grpc_rb_cXdsChannelCredentials, "compose",
grpc_rb_xds_channel_credentials_compose, -1);
id_fallback_creds = rb_intern("__fallback_creds");
}
/* Gets the wrapped grpc_channel_credentials from the ruby wrapper */
grpc_channel_credentials* grpc_rb_get_wrapped_xds_channel_credentials(VALUE v) {
grpc_rb_xds_channel_credentials* wrapper = NULL;
Check_TypedStruct(v, &grpc_rb_xds_channel_credentials_data_type);
TypedData_Get_Struct(v, grpc_rb_xds_channel_credentials,
&grpc_rb_xds_channel_credentials_data_type, wrapper);
return wrapper->wrapped;
}
bool grpc_rb_is_xds_channel_credentials(VALUE v) {
return rb_typeddata_is_kind_of(v, &grpc_rb_xds_channel_credentials_data_type);
}

@ -0,0 +1,35 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef GRPC_RB_XDS_CHANNEL_CREDENTIALS_H_
#define GRPC_RB_XDS_CHANNEL_CREDENTIALS_H_
#include <grpc/grpc_security.h>
#include <ruby/ruby.h>
#include <stdbool.h>
/* Initializes the ruby ChannelCredentials class. */
void Init_grpc_xds_channel_credentials();
/* Gets the wrapped credentials from the ruby wrapper */
grpc_channel_credentials* grpc_rb_get_wrapped_xds_channel_credentials(VALUE v);
/* Check if v is kind of XdsChannelCredentials */
bool grpc_rb_is_xds_channel_credentials(VALUE v);
#endif /* GRPC_RB_XDS_CHANNEL_CREDENTIALS_H_ */

@ -0,0 +1,169 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "rb_xds_server_credentials.h"
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
#include <grpc/support/log.h>
#include <ruby/ruby.h>
#include "rb_grpc.h"
#include "rb_grpc_imports.generated.h"
#include "rb_server_credentials.h"
/* grpc_rb_cXdsServerCredentials is the ruby class that proxies
grpc_server_credentials. */
static VALUE grpc_rb_cXdsServerCredentials = Qnil;
/* grpc_rb_xds_server_credentials wraps a grpc_server_credentials. It provides
a peer ruby object, 'mark' to hold references to objects involved in
constructing the server credentials. */
typedef struct grpc_rb_xds_server_credentials {
/* Holder of ruby objects involved in constructing the server credentials */
VALUE mark;
/* The actual server credentials */
grpc_server_credentials* wrapped;
} grpc_rb_xds_server_credentials;
/* Destroys the server credentials instances. */
static void grpc_rb_xds_server_credentials_free_internal(void* p) {
grpc_rb_xds_server_credentials* wrapper = NULL;
if (p == NULL) {
return;
};
wrapper = (grpc_rb_xds_server_credentials*)p;
/* Delete the wrapped object if the mark object is Qnil, which indicates that
no other object is the actual owner. */
if (wrapper->wrapped != NULL && wrapper->mark == Qnil) {
grpc_server_credentials_release(wrapper->wrapped);
wrapper->wrapped = NULL;
}
xfree(p);
}
/* Destroys the server credentials instances. */
static void grpc_rb_xds_server_credentials_free(void* p) {
grpc_rb_xds_server_credentials_free_internal(p);
grpc_ruby_shutdown();
}
/* Protects the mark object from GC */
static void grpc_rb_xds_server_credentials_mark(void* p) {
if (p == NULL) {
return;
}
grpc_rb_xds_server_credentials* wrapper = (grpc_rb_xds_server_credentials*)p;
/* If it's not already cleaned up, mark the mark object */
if (wrapper->mark != Qnil) {
rb_gc_mark(wrapper->mark);
}
}
static const rb_data_type_t grpc_rb_xds_server_credentials_data_type = {
"grpc_xds_server_credentials",
{grpc_rb_xds_server_credentials_mark, grpc_rb_xds_server_credentials_free,
GRPC_RB_MEMSIZE_UNAVAILABLE, NULL},
NULL,
NULL,
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
RUBY_TYPED_FREE_IMMEDIATELY
#endif
};
/* Allocates ServerCredential instances.
Provides safe initial defaults for the instance fields. */
static VALUE grpc_rb_xds_server_credentials_alloc(VALUE cls) {
grpc_ruby_init();
grpc_rb_xds_server_credentials* wrapper =
ALLOC(grpc_rb_xds_server_credentials);
wrapper->wrapped = NULL;
wrapper->mark = Qnil;
return TypedData_Wrap_Struct(cls, &grpc_rb_xds_server_credentials_data_type,
wrapper);
}
/* The attribute used on the mark object to preserve the fallback_creds. */
static ID id_fallback_creds;
/*
call-seq:
creds = ServerCredentials.new(fallback_creds)
fallback_creds: (ServerCredentials) fallback credentials to create
XDS credentials.
Initializes ServerCredential instances. */
static VALUE grpc_rb_xds_server_credentials_init(VALUE self,
VALUE fallback_creds) {
grpc_rb_xds_server_credentials* wrapper = NULL;
grpc_server_credentials* creds = NULL;
grpc_server_credentials* grpc_fallback_creds =
grpc_rb_get_wrapped_server_credentials(fallback_creds);
creds = grpc_xds_server_credentials_create(grpc_fallback_creds);
if (creds == NULL) {
rb_raise(rb_eRuntimeError,
"the call to grpc_xds_server_credentials_create() failed, could "
"not create a credentials, see "
"https://github.com/grpc/grpc/blob/master/TROUBLESHOOTING.md for "
"debugging tips");
return Qnil;
}
TypedData_Get_Struct(self, grpc_rb_xds_server_credentials,
&grpc_rb_xds_server_credentials_data_type, wrapper);
wrapper->wrapped = creds;
/* Add the input objects as hidden fields to preserve them. */
rb_ivar_set(self, id_fallback_creds, fallback_creds);
return self;
}
void Init_grpc_xds_server_credentials() {
grpc_rb_cXdsServerCredentials = rb_define_class_under(
grpc_rb_mGrpcCore, "XdsServerCredentials", rb_cObject);
/* Allocates an object managed by the ruby runtime */
rb_define_alloc_func(grpc_rb_cXdsServerCredentials,
grpc_rb_xds_server_credentials_alloc);
/* Provides a ruby constructor and support for dup/clone. */
rb_define_method(grpc_rb_cXdsServerCredentials, "initialize",
grpc_rb_xds_server_credentials_init, 1);
rb_define_method(grpc_rb_cXdsServerCredentials, "initialize_copy",
grpc_rb_cannot_init_copy, 1);
id_fallback_creds = rb_intern("__fallback_creds");
}
/* Gets the wrapped grpc_server_credentials from the ruby wrapper */
grpc_server_credentials* grpc_rb_get_wrapped_xds_server_credentials(VALUE v) {
grpc_rb_xds_server_credentials* wrapper = NULL;
Check_TypedStruct(v, &grpc_rb_xds_server_credentials_data_type);
TypedData_Get_Struct(v, grpc_rb_xds_server_credentials,
&grpc_rb_xds_server_credentials_data_type, wrapper);
return wrapper->wrapped;
}
/* Check if v is kind of ServerCredentials */
bool grpc_rb_is_xds_server_credentials(VALUE v) {
return rb_typeddata_is_kind_of(v, &grpc_rb_xds_server_credentials_data_type);
}

@ -0,0 +1,35 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef GRPC_RB_XDS_SERVER_CREDENTIALS_H_
#define GRPC_RB_XDS_SERVER_CREDENTIALS_H_
#include <grpc/grpc_security.h>
#include <ruby/ruby.h>
#include <stdbool.h>
/* Initializes the ruby XdsServerCredentials class. */
void Init_grpc_xds_server_credentials();
/* Gets the wrapped server_credentials from the ruby wrapper */
grpc_server_credentials* grpc_rb_get_wrapped_xds_server_credentials(VALUE v);
/* Check if v is kind of XdsServerCredentials */
bool grpc_rb_is_xds_server_credentials(VALUE v);
#endif /* GRPC_RB_XDS_SERVER_CREDENTIALS_H_ */

@ -41,8 +41,10 @@ module GRPC
channel_args['grpc.primary_user_agent'] += ' ' channel_args['grpc.primary_user_agent'] += ' '
end end
channel_args['grpc.primary_user_agent'] += "grpc-ruby/#{VERSION}" channel_args['grpc.primary_user_agent'] += "grpc-ruby/#{VERSION}"
unless creds.is_a?(Core::ChannelCredentials) || creds.is_a?(Symbol) unless creds.is_a?(Core::ChannelCredentials) ||
fail(TypeError, '!ChannelCredentials or Symbol') creds.is_a?(Core::XdsChannelCredentials) ||
creds.is_a?(Symbol)
fail(TypeError, 'creds is not a ChannelCredentials, XdsChannelCredentials, or Symbol')
end end
Core::Channel.new(host, channel_args, creds) Core::Channel.new(host, channel_args, creds)
end end

@ -60,6 +60,38 @@ describe GRPC::Core::ChannelCredentials do
blk = proc { GRPC::Core::ChannelCredentials.new(nil, '', nil) } blk = proc { GRPC::Core::ChannelCredentials.new(nil, '', nil) }
expect(&blk).to raise_error expect(&blk).to raise_error
end end
it 'can be constructed with a fallback credential' do
blk = proc {
fallback = GRPC::Core::ChannelCredentials.new
GRPC::Core::XdsChannelCredentials.new(fallback)
}
expect(&blk).not_to raise_error
end
it 'fails gracefully constructed with nil' do
blk = proc {
GRPC::Core::XdsChannelCredentials.new(nil)
}
expect(&blk).to raise_error TypeError, /expected grpc_channel_credentials/
end
it 'fails gracefully constructed with a non-C-extension object' do
blk = proc {
not_a_fallback = 100
GRPC::Core::XdsChannelCredentials.new(not_a_fallback)
}
expect(&blk).to raise_error TypeError, /expected grpc_channel_credentials/
end
it 'fails gracefully constructed with a non-ChannelCredentials object' do
blk = proc {
not_a_fallback = GRPC::Core::Channel.new('dummy_host', nil,
:this_channel_is_insecure)
GRPC::Core::XdsChannelCredentials.new(not_a_fallback)
}
expect(&blk).to raise_error TypeError, /expected grpc_channel_credentials/
end
end end
describe '#compose' do describe '#compose' do

@ -130,6 +130,17 @@ describe GRPC::Core::Channel do
end end
end end
describe '#new for XDS channels' do
it_behaves_like '#new'
def construct_with_args(a)
proc do
xds_creds = GRPC::Core::XdsChannelCredentials.new(create_test_cert)
GRPC::Core::Channel.new('dummy_host', a, xds_creds)
end
end
end
describe '#create_call' do describe '#create_call' do
it 'creates a call OK' do it 'creates a call OK' do
ch = GRPC::Core::Channel.new(fake_host, nil, :this_channel_is_insecure) ch = GRPC::Core::Channel.new(fake_host, nil, :this_channel_is_insecure)

@ -85,7 +85,10 @@ describe 'client-server auth' do
poll_period: 1 poll_period: 1
} }
@srv = new_rpc_server_for_testing(**server_opts) @srv = new_rpc_server_for_testing(**server_opts)
port = @srv.add_http2_port('0.0.0.0:0', create_server_creds) ssl_creds = create_server_creds
xds_creds = GRPC::Core::XdsServerCredentials.new(ssl_creds)
port = @srv.add_http2_port('0.0.0.0:0', ssl_creds)
xds_port = @srv.add_http2_port('0.0.0.0:0', xds_creds)
@srv.handle(SslTestService) @srv.handle(SslTestService)
@srv_thd = Thread.new { @srv.run } @srv_thd = Thread.new { @srv.run }
@srv.wait_till_running @srv.wait_till_running
@ -98,6 +101,11 @@ describe 'client-server auth' do
@stub = SslTestServiceStub.new("localhost:#{port}", @stub = SslTestServiceStub.new("localhost:#{port}",
create_channel_creds, create_channel_creds,
**client_opts) **client_opts)
# auth should success as the fallback creds wil be used
xds_channel_creds = GRPC::Core::XdsChannelCredentials.new(create_channel_creds)
@xds_stub = SslTestServiceStub.new("localhost:#{xds_port}",
xds_channel_creds,
**client_opts)
end end
after(:all) do after(:all) do
@ -123,4 +131,22 @@ describe 'client-server auth' do
responses = @stub.a_bidi_rpc([EchoMsg.new, EchoMsg.new]) responses = @stub.a_bidi_rpc([EchoMsg.new, EchoMsg.new])
responses.each { |r| GRPC.logger.info(r) } responses.each { |r| GRPC.logger.info(r) }
end end
it 'xds_client-xds_server ssl fallback auth with unary RPCs' do
@xds_stub.an_rpc(EchoMsg.new)
end
it 'xds_client-xds_server ssl fallback auth with client streaming RPCs' do
@xds_stub.a_client_streaming_rpc([EchoMsg.new, EchoMsg.new])
end
it 'xds_client-xds_server ssl fallback auth with server streaming RPCs' do
responses = @xds_stub.a_server_streaming_rpc(EchoMsg.new)
responses.each { |r| GRPC.logger.info(r) }
end
it 'xds_client-xds_server ssl fallback auth with bidi RPCs' do
responses = @xds_stub.a_bidi_rpc([EchoMsg.new, EchoMsg.new])
responses.each { |r| GRPC.logger.info(r) }
end
end end

@ -23,6 +23,7 @@ end
describe GRPC::Core::ServerCredentials do describe GRPC::Core::ServerCredentials do
Creds = GRPC::Core::ServerCredentials Creds = GRPC::Core::ServerCredentials
XdsCreds = GRPC::Core::XdsServerCredentials
describe '#new' do describe '#new' do
it 'can be constructed from a fake CA PEM, server PEM and a server key' do it 'can be constructed from a fake CA PEM, server PEM and a server key' do
@ -75,5 +76,29 @@ describe GRPC::Core::ServerCredentials do
blk = proc { Creds.new(nil, cert_pairs, false) } blk = proc { Creds.new(nil, cert_pairs, false) }
expect(&blk).to_not raise_error expect(&blk).to_not raise_error
end end
it 'can be constructed with a fallback credential' do
_, cert_pairs, _ = load_test_certs
fallback = Creds.new(nil, cert_pairs, false)
blk = proc { XdsCreds.new(fallback) }
expect(&blk).to_not raise_error
end
it 'cannot be constructed with nil' do
blk = proc { XdsCreds.new(nil) }
expect(&blk).to raise_error TypeError, /expected grpc_server_credentials/
end
it 'cannot be constructed with a non-C-extension object' do
not_a_fallback = 100
blk = proc { XdsCreds.new(not_a_fallback) }
expect(&blk).to raise_error TypeError, /expected grpc_server_credentials/
end
it 'cannot be constructed with a non-ServerCredentials object' do
not_a_fallback = GRPC::Core::ChannelCredentials.new
blk = proc { XdsCreds.new(not_a_fallback) }
expect(&blk).to raise_error TypeError, /expected grpc_server_credentials/
end
end end
end end

@ -139,6 +139,28 @@ describe Server do
expect(&blk).to raise_error(RuntimeError) expect(&blk).to raise_error(RuntimeError)
end end
end end
describe 'for xds servers' do
let(:cert) { create_test_cert }
let(:xds) { GRPC::Core::XdsServerCredentials.new(cert) }
it 'runs without failing' do
blk = proc do
s = new_core_server_for_testing(nil)
s.add_http2_port('localhost:0', xds)
s.shutdown_and_notify(nil)
s.close
end
expect(&blk).to_not raise_error
end
it 'fails if the server is closed' do
s = new_core_server_for_testing(nil)
s.shutdown_and_notify(nil)
s.close
blk = proc { s.add_http2_port('localhost:0', xds) }
expect(&blk).to raise_error(RuntimeError)
end
end
end end
shared_examples '#new' do shared_examples '#new' do

Loading…
Cancel
Save