1# -*- coding: utf-8 -*- #
2# Copyright 2017 Google LLC. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Utility functions for Cloud KMS integration with GCE.
16
17Collection of methods to handle Cloud KMS (Key Management Service) resources
18with Google Compute Engine (GCE).
19"""
20
21from __future__ import absolute_import
22from __future__ import division
23from __future__ import unicode_literals
24
25from googlecloudsdk.calliope import exceptions as calliope_exceptions
26from googlecloudsdk.core import properties
27from googlecloudsdk.core import resources
28
29KMS_HELP_URL = ('https://cloud.google.com/compute/docs/disks/'
30                'customer-managed-encryption')
31_KMS_ARGS = ['kms-key', 'kms-keyring', 'kms-location', 'kms-project',
32             'boot-disk-kms-key', 'boot-disk-kms-keyring',
33             'boot-disk-kms-location', 'boot-disk-kms-project']
34
35
36def _GetSpecifiedKmsArgs(args):
37  """Returns the first KMS related argument as a string."""
38  if not args:
39    return None
40  specified = set()
41  for keyword in _KMS_ARGS:
42    if getattr(args, keyword.replace('-', '_'), None):
43      specified.add('--' + keyword)
44  return specified
45
46
47def _GetSpecifiedKmsDict(args):
48  """Returns the first KMS related argument as a string."""
49  if not args:
50    return None
51  specified = set()
52  for keyword in _KMS_ARGS:
53    if keyword in args:
54      specified.add(keyword)
55  return specified
56
57
58def _DictToKmsKey(args):
59  """Returns the Cloud KMS crypto key name based on the KMS args."""
60  if not args:
61    return None
62
63  def GetValue(args, key):
64    def GetValueFunc():
65      val = args[key] if key in args else None
66      if val:
67        return val
68      raise calliope_exceptions.InvalidArgumentException(
69          '--create-disk',
70          'KMS cryptokey resource was not fully specified. Key [{}] must '
71          'be specified.'.format(key))
72    return GetValueFunc
73
74  return resources.REGISTRY.Parse(
75      GetValue(args, 'kms-key')(),
76      params={
77          'projectsId': args['kms-project'] if 'kms-project' in args else
78                        properties.VALUES.core.project.GetOrFail,
79          'locationsId': GetValue(args, 'kms-location'),
80          'keyRingsId': GetValue(args, 'kms-keyring'),
81          'cryptoKeysId': GetValue(args, 'kms-key'),
82      },
83      collection='cloudkms.projects.locations.keyRings.cryptoKeys')
84
85
86def _DictToMessage(args, messages):
87  """Returns the Cloud KMS crypto key name based on the values in the dict."""
88  key = _DictToKmsKey(args)
89  if not key:
90    return None
91  return messages.CustomerEncryptionKey(kmsKeyName=key.RelativeName())
92
93
94def MaybeGetKmsKey(args, messages, current_value, boot_disk_prefix=False):
95  """Gets the Cloud KMS CryptoKey reference from command arguments.
96
97  Args:
98    args: Namespaced command line arguments.
99    messages: Compute API messages module.
100    current_value: Current CustomerEncryptionKey value.
101    boot_disk_prefix: If the key flags have the 'boot-disk' prefix.
102
103  Returns:
104    CustomerEncryptionKey message with the KMS key populated if args has a key.
105  Raises:
106    ConflictingArgumentsException if an encryption key is already populated.
107  """
108  key_arg = args.CONCEPTS.kms_key
109  key = key_arg.Parse()
110  if bool(_GetSpecifiedKmsArgs(args)) and not key:
111    flag = '--boot-disk-kms-key' if boot_disk_prefix else '--kms-key'
112    raise calliope_exceptions.InvalidArgumentException(
113        flag, 'KMS cryptokey resource was not fully specified.')
114  if key:
115    if current_value:
116      raise calliope_exceptions.ConflictingArgumentsException(
117          '--csek-key-file', *_GetSpecifiedKmsArgs(args))
118    return messages.CustomerEncryptionKey(kmsKeyName=key.RelativeName())
119  return current_value
120
121
122def MaybeGetKmsKeyFromDict(args, messages, current_value,
123                           conflicting_arg='--csek-key-file'):
124  """Gets the Cloud KMS CryptoKey reference for a boot disk's initialize params.
125
126  Args:
127    args: A dictionary of a boot disk's initialize params.
128    messages: Compute API messages module.
129    current_value: Current CustomerEncryptionKey value.
130    conflicting_arg: name of conflicting argument
131
132  Returns:
133    CustomerEncryptionKey message with the KMS key populated if args has a key.
134  Raises:
135    ConflictingArgumentsException if an encryption key is already populated.
136  """
137  if bool(_GetSpecifiedKmsDict(args)):
138    if current_value:
139      raise calliope_exceptions.ConflictingArgumentsException(
140          conflicting_arg, *_GetSpecifiedKmsArgs(args))
141    return _DictToMessage(args, messages)
142  return current_value
143