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"""Helpers for parsing flags and arguments."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import unicode_literals
20
21from googlecloudsdk.calliope import arg_parsers
22from googlecloudsdk.calliope import base
23from googlecloudsdk.command_lib.kms import maps
24from googlecloudsdk.command_lib.util import completers
25from googlecloudsdk.command_lib.util import parameter_info_lib
26from googlecloudsdk.core import properties
27from googlecloudsdk.core import resources
28from googlecloudsdk.core.util import times
29
30KEY_RING_COLLECTION = 'cloudkms.projects.locations.keyRings'
31LOCATION_COLLECTION = 'cloudkms.projects.locations'
32
33# Collection names.
34CRYPTO_KEY_COLLECTION = 'cloudkms.projects.locations.keyRings.cryptoKeys'
35CRYPTO_KEY_VERSION_COLLECTION = '%s.cryptoKeyVersions' % CRYPTO_KEY_COLLECTION
36IMPORT_JOB_COLLECTION = 'cloudkms.projects.locations.keyRings.importJobs'
37# list command aggregators
38
39
40class ListCommandParameterInfo(parameter_info_lib.ParameterInfoByConvention):
41
42  def GetFlag(self,
43              parameter_name,
44              parameter_value=None,
45              check_properties=True,
46              for_update=False):
47    return super(ListCommandParameterInfo, self).GetFlag(
48        parameter_name,
49        parameter_value=parameter_value,
50        check_properties=check_properties,
51        for_update=for_update,
52    )
53
54
55class ListCommandCompleter(completers.ListCommandCompleter):
56
57  def ParameterInfo(self, parsed_args, argument):
58    return ListCommandParameterInfo(
59        parsed_args,
60        argument,
61        self.collection,
62        updaters=COMPLETERS_BY_CONVENTION,
63    )
64
65
66# kms completers
67
68
69class LocationCompleter(ListCommandCompleter):
70
71  def __init__(self, **kwargs):
72    super(LocationCompleter, self).__init__(
73        collection=LOCATION_COLLECTION,
74        list_command='kms locations list --uri',
75        **kwargs)
76
77
78class KeyRingCompleter(ListCommandCompleter):
79
80  def __init__(self, **kwargs):
81    super(KeyRingCompleter, self).__init__(
82        collection=KEY_RING_COLLECTION,
83        list_command='kms keyrings list --uri',
84        flags=['location'],
85        **kwargs)
86
87
88class KeyCompleter(ListCommandCompleter):
89
90  def __init__(self, **kwargs):
91    super(KeyCompleter, self).__init__(
92        collection=CRYPTO_KEY_COLLECTION,
93        list_command='kms keys list --uri',
94        flags=['location', 'keyring'],
95        **kwargs)
96
97
98class KeyVersionCompleter(ListCommandCompleter):
99
100  def __init__(self, **kwargs):
101    super(KeyVersionCompleter, self).__init__(
102        collection=CRYPTO_KEY_VERSION_COLLECTION,
103        list_command='kms keys versions list --uri',
104        flags=['location', 'key', 'keyring'],
105        **kwargs)
106
107
108@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
109class ImportJobCompleter(ListCommandCompleter):
110
111  def __init__(self, **kwargs):
112    super(ImportJobCompleter, self).__init__(
113        collection=IMPORT_JOB_COLLECTION,
114        list_command='beta kms import-jobs list --uri',
115        flags=['location', 'keyring'],
116        **kwargs)
117
118
119# completers by parameter name convention
120
121COMPLETERS_BY_CONVENTION = {
122    'location': (LocationCompleter, False),
123    'keyring': (KeyRingCompleter, False),
124    'key': (KeyCompleter, False),
125    'import-jobs': (ImportJobCompleter, False),
126}
127
128
129# Flags.
130def AddLocationFlag(parser, resource='resource'):
131  parser.add_argument(
132      '--location',
133      completer=LocationCompleter,
134      help='Location of the {0}.'.format(resource))
135
136
137def AddKeyRingFlag(parser, resource='resource'):
138  parser.add_argument(
139      '--keyring',
140      completer=KeyRingCompleter,
141      help='Key ring of the {0}.'.format(resource))
142
143
144def AddCryptoKeyFlag(parser, help_text=None):
145  parser.add_argument(
146      '--key', completer=KeyCompleter, help=help_text or 'The containing key.')
147
148
149def AddKeyResourceFlags(parser, help_text=None):
150  AddLocationFlag(parser, 'keyring')
151  AddKeyRingFlag(parser, 'key')
152  AddCryptoKeyFlag(parser, help_text)
153
154
155def AddCryptoKeyVersionFlag(parser, help_action, required=False):
156  parser.add_argument(
157      '--version',
158      required=required,
159      completer=KeyVersionCompleter,
160      help='Version {0}.'.format(help_action))
161
162
163def AddCryptoKeyPrimaryVersionFlag(parser, help_action, required=False):
164  parser.add_argument(
165      '--primary-version',
166      required=required,
167      completer=KeyVersionCompleter,
168      help='Primary version {0}.'.format(help_action))
169
170
171def AddRotationPeriodFlag(parser):
172  parser.add_argument(
173      '--rotation-period',
174      type=arg_parsers.Duration(lower_bound='1d'),
175      help=('Automatic rotation period of the key. See '
176            '$ gcloud topic datetimes for information on duration formats.'))
177
178
179def AddNextRotationTimeFlag(parser):
180  parser.add_argument(
181      '--next-rotation-time',
182      type=arg_parsers.Datetime.Parse,
183      help=('Next automatic rotation time of the key. See '
184            '$ gcloud topic datetimes for information on time formats.'))
185
186
187def AddRemoveRotationScheduleFlag(parser):
188  parser.add_argument(
189      '--remove-rotation-schedule',
190      action='store_true',
191      help='Remove any existing rotation schedule on the key.')
192
193
194def AddSkipInitialVersionCreationFlag(parser):
195  parser.add_argument(
196      '--skip-initial-version-creation',
197      default=None,
198      action='store_true',
199      dest='skip_initial_version_creation',
200      help=('Skip creating the first version in a key and setting it as '
201            'primary during creation.'))
202
203
204def AddPlaintextFileFlag(parser, help_action):
205  parser.add_argument(
206      '--plaintext-file',
207      help='File path of the plaintext file {0}.'.format(help_action),
208      required=True)
209
210
211def AddCiphertextFileFlag(parser, help_action):
212  parser.add_argument(
213      '--ciphertext-file',
214      help='File path of the ciphertext file {0}.'.format(help_action),
215      required=True)
216
217
218def AddSignatureFileFlag(parser, help_action):
219  parser.add_argument(
220      '--signature-file',
221      help='Path to the signature file {}.'.format(help_action),
222      required=True)
223
224
225def AddInputFileFlag(parser, help_action):
226  parser.add_argument(
227      '--input-file',
228      help='Path to the input file {}.'.format(help_action),
229      required=True)
230
231
232def AddRsaAesWrappedKeyFileFlag(parser, help_action):
233  parser.add_argument(
234      '--rsa-aes-wrapped-key-file',
235      help='Path to the wrapped RSA AES key file {}.'.format(help_action))
236
237
238def AddOutputFileFlag(parser, help_action):
239  parser.add_argument(
240      '--output-file', help='Path to the output file {}.'.format(help_action))
241
242
243def AddAadFileFlag(parser):
244  parser.add_argument(
245      '--additional-authenticated-data-file',
246      help='File path to the optional file containing the additional '
247      'authenticated data.')
248
249
250def AddProtectionLevelFlag(parser):
251  parser.add_argument(
252      '--protection-level',
253      choices=['software', 'hsm', 'external'],
254      default='software',
255      help='Protection level of the key.')
256
257
258def AddRequiredProtectionLevelFlag(parser):
259  parser.add_argument(
260      '--protection-level',
261      choices=['software', 'hsm'],
262      help='Protection level of the import job.',
263      required=True)
264
265
266def AddAttestationFileFlag(parser):
267  parser.add_argument(
268      '--attestation-file', help='Path to the output attestation file.')
269
270
271def AddDefaultAlgorithmFlag(parser):
272  parser.add_argument(
273      '--default-algorithm',
274      choices=sorted(maps.ALL_ALGORITHMS),
275      help='The default algorithm for the crypto key. For more information '
276      'about choosing an algorithm, see '
277      'https://cloud.google.com/kms/docs/algorithms.')
278
279
280def AddRequiredImportMethodFlag(parser):
281  parser.add_argument(
282      '--import-method',
283      choices=sorted(maps.IMPORT_METHOD_MAPPER.choices)[1:],
284      help='The wrapping method to be used for incoming key material. For more '
285      'information about choosing an import method, see '
286      'https://cloud.google.com/kms/docs/key-wrapping.',
287      required=True)
288
289
290def AddOptionalPublicKeyFileArgument(parser):
291  parser.add_argument(
292      '--public-key-file',
293      help='Optional path to the public key of the ImportJob, used to wrap the '
294      'key for import. If missing, the public key will be fetched on your '
295      'behalf.')
296
297
298def AddOptionalTargetKeyFileArgument(parser):
299  parser.add_argument(
300      '--target-key-file',
301      help='Optional path to the unwrapped target key to import into a Cloud '
302      'KMS key version. If specified, the key will be securely wrapped before '
303      'transmission to Google.')
304
305
306def AddDigestAlgorithmFlag(parser, help_action):
307  parser.add_argument(
308      '--digest-algorithm',
309      choices=sorted(maps.DIGESTS),
310      help=help_action,
311      required=True)
312
313
314def AddImportedVersionAlgorithmFlag(parser):
315  parser.add_argument(
316      '--algorithm',
317      choices=sorted(maps.ALGORITHMS_FOR_IMPORT),
318      help='The algorithm to assign to the new key version. For more '
319      'information about supported algorithms, see '
320      'https://cloud.google.com/kms/docs/algorithms.',
321      required=True)
322
323
324def AddExternalKeyUriFlag(parser):
325  parser.add_argument(
326      '--external-key-uri',
327      suggestion_aliases=['--key-uri'],
328      help='The URI of the external key for keys with protection level'
329      ' "external".')
330
331
332def AddStateFlag(parser):
333  parser.add_argument('--state', dest='state', help='State of the key version.')
334
335
336def AddSkipIntegrityVerification(parser):
337  parser.add_argument(
338      '--skip-integrity-verification',
339      default=None,
340      action='store_true',
341      dest='skip_integrity_verification',
342      help=('Skip integrity verification on request and response API fields.'))
343
344
345# Arguments
346def AddKeyRingArgument(parser, help_action):
347  parser.add_argument(
348      'keyring',
349      completer=KeyRingCompleter,
350      help='Name of the key ring {0}.'.format(help_action))
351
352
353def AddCryptoKeyArgument(parser, help_action):
354  parser.add_argument(
355      'key',
356      completer=KeyCompleter,
357      help='Name of the key {0}.'.format(help_action))
358
359
360def AddKeyResourceArgument(parser, help_action):
361  AddLocationFlag(parser, 'key')
362  AddKeyRingFlag(parser, 'key')
363  AddCryptoKeyArgument(parser, help_action)
364
365
366def AddCryptoKeyVersionArgument(parser, help_action):
367  parser.add_argument(
368      'version',
369      completer=KeyVersionCompleter,
370      help='Name of the version {0}.'.format(help_action))
371
372
373def AddKeyVersionResourceArgument(parser, help_action):
374  AddKeyResourceFlags(parser)
375  AddCryptoKeyVersionArgument(parser, help_action)
376
377
378def AddPositionalImportJobArgument(parser, help_action):
379  parser.add_argument(
380      'import_job',
381      completer=ImportJobCompleter,
382      help='Name of the import job {0}.'.format(help_action))
383
384
385def AddRequiredImportJobArgument(parser, help_action):
386  parser.add_argument(
387      '--import-job',
388      completer=ImportJobCompleter,
389      help='Name of the import job {0}.'.format(help_action),
390      required=True)
391
392
393def AddCertificateChainFlag(parser):
394  parser.add_argument(
395      '--certificate-chain-type',
396      default='all',
397      choices=['all', 'cavium', 'google-card', 'google-partition'],
398      help='Certificate chain to retrieve.')
399
400
401# Parsing.
402def ParseLocationName(args):
403  return resources.REGISTRY.Parse(
404      args.location,
405      params={'projectsId': properties.VALUES.core.project.GetOrFail},
406      collection=LOCATION_COLLECTION)
407
408
409def ParseKeyRingName(args):
410  return resources.REGISTRY.Parse(
411      args.keyring,
412      params={
413          'projectsId': properties.VALUES.core.project.GetOrFail,
414          'locationsId': args.MakeGetOrRaise('--location'),
415      },
416      collection=KEY_RING_COLLECTION)
417
418
419def ParseCryptoKeyName(args):
420  return resources.REGISTRY.Parse(
421      args.key,
422      params={
423          'keyRingsId': args.MakeGetOrRaise('--keyring'),
424          'locationsId': args.MakeGetOrRaise('--location'),
425          'projectsId': properties.VALUES.core.project.GetOrFail,
426      },
427      collection=CRYPTO_KEY_COLLECTION)
428
429
430def ParseCryptoKeyVersionName(args):
431  return resources.REGISTRY.Parse(
432      args.version,
433      params={
434          'cryptoKeysId': args.MakeGetOrRaise('--key'),
435          'keyRingsId': args.MakeGetOrRaise('--keyring'),
436          'locationsId': args.MakeGetOrRaise('--location'),
437          'projectsId': properties.VALUES.core.project.GetOrFail,
438      },
439      collection=CRYPTO_KEY_VERSION_COLLECTION)
440
441
442def ParseImportJobName(args):
443  return resources.REGISTRY.Parse(
444      args.import_job,
445      params={
446          'keyRingsId': args.MakeGetOrRaise('--keyring'),
447          'locationsId': args.MakeGetOrRaise('--location'),
448          'projectsId': properties.VALUES.core.project.GetOrFail,
449      },
450      collection=IMPORT_JOB_COLLECTION)
451
452
453# Get parent type Resource from output of Parse functions above.
454def ParseParentFromResource(resource_ref):
455  collection_list = resource_ref.Collection().split('.')
456  parent_collection = '.'.join(collection_list[:-1])
457  params = resource_ref.AsDict()
458  del params[collection_list[-1] + 'Id']
459  return resources.REGISTRY.Create(parent_collection, **params)
460
461
462# Set proto fields from flags.
463def SetRotationPeriod(args, crypto_key):
464  if args.rotation_period is not None:
465    crypto_key.rotationPeriod = '{0}s'.format(args.rotation_period)
466
467
468def SetNextRotationTime(args, crypto_key):
469  if args.next_rotation_time is not None:
470    crypto_key.nextRotationTime = times.FormatDateTime(args.next_rotation_time)
471