sdk.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package ydxcommon
  14. import (
  15. "context"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "errors"
  19. "time"
  20. "github.com/yandex-cloud/go-genproto/yandex/cloud/endpoint"
  21. ycsdk "github.com/yandex-cloud/go-sdk"
  22. "github.com/yandex-cloud/go-sdk/iamkey"
  23. "google.golang.org/grpc"
  24. "google.golang.org/grpc/credentials"
  25. "google.golang.org/grpc/keepalive"
  26. )
  27. // NewGrpcConnection creates a connection to the given Yandex.Cloud API endpoint.
  28. func NewGrpcConnection(
  29. ctx context.Context,
  30. apiEndpoint string,
  31. apiEndpointID string, // an ID from https://api.cloud.yandex.net/endpoints
  32. authorizedKey *iamkey.Key,
  33. caCertificate []byte,
  34. ) (*grpc.ClientConn, error) {
  35. tlsConf, err := tlsConfig(caCertificate)
  36. if err != nil {
  37. return nil, err
  38. }
  39. sdk, err := buildSDK(ctx, apiEndpoint, authorizedKey, tlsConf)
  40. if err != nil {
  41. return nil, err
  42. }
  43. defer func() {
  44. _ = closeSDK(ctx, sdk)
  45. }()
  46. serviceAPIEndpoint, err := sdk.ApiEndpoint().ApiEndpoint().Get(ctx, &endpoint.GetApiEndpointRequest{
  47. ApiEndpointId: apiEndpointID,
  48. })
  49. if err != nil {
  50. return nil, err
  51. }
  52. // Until gRPC proposal A61 is implemented in grpc-go, default gRPC name resolver (dns)
  53. // is incompatible with dualstack backends, and YC API backends are dualstack.
  54. // However, if passthrough resolver is used instead, grpc-go won't do any name resolution
  55. // and will pass the endpoint to net.Dial as-is, which would utilize happy-eyeballs
  56. // support in Go's net package.
  57. // So we explicitly set gRPC resolver to `passthrough` to match `ycsdk`s behavior,
  58. // which uses `passthrough` resolver implicitly by using deprecated grpc.DialContext
  59. // instead of grpc.NewClient used here
  60. target := "passthrough:///" + serviceAPIEndpoint.Address
  61. return grpc.NewClient(target,
  62. grpc.WithTransportCredentials(credentials.NewTLS(tlsConf)),
  63. grpc.WithKeepaliveParams(keepalive.ClientParameters{
  64. Time: time.Second * 30,
  65. Timeout: time.Second * 10,
  66. PermitWithoutStream: false,
  67. }),
  68. grpc.WithUserAgent("external-secrets"),
  69. )
  70. }
  71. // NewIamToken exchanges the given authorized key to an IAM token.
  72. func NewIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (*IamToken, error) {
  73. config, err := tlsConfig(caCertificate)
  74. if err != nil {
  75. return nil, err
  76. }
  77. sdk, err := buildSDK(ctx, apiEndpoint, authorizedKey, config)
  78. if err != nil {
  79. return nil, err
  80. }
  81. defer func() {
  82. _ = closeSDK(ctx, sdk)
  83. }()
  84. iamToken, err := sdk.CreateIAMToken(ctx)
  85. if err != nil {
  86. return nil, err
  87. }
  88. return &IamToken{Token: iamToken.IamToken, ExpiresAt: iamToken.ExpiresAt.AsTime()}, nil
  89. }
  90. func tlsConfig(caCertificate []byte) (*tls.Config, error) {
  91. config := &tls.Config{MinVersion: tls.VersionTLS12}
  92. if caCertificate != nil {
  93. caCertPool := x509.NewCertPool()
  94. ok := caCertPool.AppendCertsFromPEM(caCertificate)
  95. if !ok {
  96. return nil, errors.New("unable to read trusted CA certificates")
  97. }
  98. config.RootCAs = caCertPool
  99. }
  100. return config, nil
  101. }
  102. func buildSDK(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, tlsConfig *tls.Config) (*ycsdk.SDK, error) {
  103. var creds ycsdk.Credentials
  104. if authorizedKey != nil {
  105. var err error
  106. creds, err = ycsdk.ServiceAccountKey(authorizedKey)
  107. if err != nil {
  108. return nil, err
  109. }
  110. } else {
  111. creds = ycsdk.InstanceServiceAccount()
  112. }
  113. sdk, err := ycsdk.Build(ctx, ycsdk.Config{
  114. Credentials: creds,
  115. Endpoint: apiEndpoint,
  116. TLSConfig: tlsConfig,
  117. })
  118. if err != nil {
  119. return nil, err
  120. }
  121. return sdk, nil
  122. }
  123. func closeSDK(ctx context.Context, sdk *ycsdk.SDK) error {
  124. return sdk.Shutdown(ctx)
  125. }
  126. // PerRPCCredentials implements the grpc.PerRPCCredentials interface for IAM token authentication.
  127. type PerRPCCredentials struct {
  128. IamToken string
  129. }
  130. // GetRequestMetadata returns the request metadata to be used in gRPC requests.
  131. func (t PerRPCCredentials) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
  132. return map[string]string{"Authorization": "Bearer " + t.IamToken}, nil
  133. }
  134. // RequireTransportSecurity indicates whether the credentials require transport security.
  135. func (PerRPCCredentials) RequireTransportSecurity() bool {
  136. return true
  137. }