Completed
Pull Request — master (#2631)
by Lakshmi
06:13
created

KeyValuePairAPI._read_crypto_key()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
dl 0
loc 5
rs 9.4285
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  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
16
import datetime
17
import json
0 ignored issues
show
Unused Code introduced by
The import json seems to be unused.
Loading history...
18
import os
19
20
from keyczar.keys import AesKey
21
from oslo_config import cfg
22
import six
23
24
from st2common.exceptions.keyvalue import CryptoKeyNotSetupException
25
from st2common.log import logging
26
from st2common.util import isotime
27
from st2common.util import date as date_utils
28
from st2common.util.crypto import symmetric_encrypt, symmetric_decrypt
29
from st2common.models.api.base import BaseAPI
30
from st2common.models.db.keyvalue import KeyValuePairDB
31
32
LOG = logging.getLogger(__name__)
33
34
class KeyValuePairAPI(BaseAPI):
35
    crypto_setup = False
36
    model = KeyValuePairDB
37
    schema = {
38
        'type': 'object',
39
        'properties': {
40
            'id': {
41
                'type': 'string'
42
            },
43
            "uid": {
44
                "type": "string"
45
            },
46
            'name': {
47
                'type': 'string'
48
            },
49
            'description': {
50
                'type': 'string'
51
            },
52
            'value': {
53
                'type': 'string',
54
                'required': True
55
            },
56
            'secret': {
57
                'type': 'boolean',
58
                'required': False,
59
                'default': False
60
            },
61
            'expire_timestamp': {
62
                'type': 'string',
63
                'pattern': isotime.ISO8601_UTC_REGEX
64
            },
65
            # Note: Those values are only used for input
66
            # TODO: Improve
67
            'ttl': {
68
                'type': 'integer'
69
            }
70
        },
71
        'additionalProperties': False
72
    }
73
74
    @staticmethod
75
    def _setup_crypto():
76
        LOG.info('Checking if encryption is enabled for key-value store.')
77
        KeyValuePairAPI.is_encryption_enabled = cfg.CONF.keyvalue.enable_encryption
78
        LOG.debug('Encryption enabled? : %s', KeyValuePairAPI.is_encryption_enabled)
79
        if KeyValuePairAPI.is_encryption_enabled:
80
            KeyValuePairAPI.crypto_key_path = cfg.CONF.keyvalue.encryption_key_path
81
            LOG.info('Encryption enabled. Looking for key in path %s',
82
                     KeyValuePairAPI.crypto_key_path)
83
            if not os.path.exists(KeyValuePairAPI.crypto_key_path):
84
                msg = ('Encryption key file does not exist in path %s.' %
85
                        KeyValuePairAPI.crypto_key_path)
86
                LOG.exception(msg)
87
                LOG.info('All API requests will now send out BAD_REQUEST ' +
88
                         'if you ask to store secrets in key value store.')
89
                KeyValuePairAPI.crypto_key = None
90
            else:
91
                KeyValuePairAPI.crypto_key = KeyValuePairAPI._read_crypto_key(
92
                    KeyValuePairAPI.crypto_key_path
93
                )
94
        KeyValuePairAPI.crypto_setup = True
95
96
    @staticmethod
97
    def _read_crypto_key(key_path):
98
        with open(key_path) as f:
99
            key = AesKey.Read(f.read())
100
            return key
101
102
    @classmethod
103
    def from_model(cls, model, mask_secrets=True):
104
        if not KeyValuePairAPI.crypto_setup:
105
            KeyValuePairAPI._setup_crypto()
106
107
        doc = cls._from_model(model, mask_secrets=mask_secrets)
108
109
        if 'id' in doc:
110
            del doc['id']
111
112
        if not mask_secrets and model.encrypted:
113
            doc['value'] = symmetric_decrypt(KeyValuePairAPI.crypto_key, model.value)
114
115
        if model.expire_timestamp:
116
            doc['expire_timestamp'] = isotime.format(model.expire_timestamp, offset=False)
117
118
        attrs = {attr: value for attr, value in six.iteritems(doc) if value is not None}
119
        return cls(**attrs)
120
121
    @classmethod
122
    def to_model(cls, kvp):
123
        if not KeyValuePairAPI.crypto_setup:
124
            KeyValuePairAPI._setup_crypto()
125
126
        name = getattr(kvp, 'name', None)
127
        description = getattr(kvp, 'description', None)
128
        value = kvp.value
129
        encrypted = False
130
131
        if getattr(kvp, 'ttl', None):
132
            expire_timestamp = (date_utils.get_datetime_utc_now() +
133
                                datetime.timedelta(seconds=kvp.ttl))
134
        else:
135
            expire_timestamp = None
136
137
        if getattr(kvp, 'secret', False):
138
            if not KeyValuePairAPI.crypto_key:
139
                msg = ('Crypto key not found in %s. Unable to encrypt value for key %s.' %
140
                        (KeyValuePairAPI.crypto_key_path, name))
141
                raise CryptoKeyNotSetupException(msg)
142
            value = symmetric_encrypt(KeyValuePairAPI.crypto_key, value)
143
            encrypted = True
144
145
        model = cls.model(name=name, description=description, value=value,
146
                          encrypted=encrypted,
147
                          expire_timestamp=expire_timestamp)
148
149
        return model
150