Skip to content

Commit

Permalink
Follow-up to "Implement support of encrypted elements in configuratio…
Browse files Browse the repository at this point in the history
…n file"

Cf. PR ClickHouse#50986

- rename XML attribute "encryption_codec" to "encrypted_by"
  • Loading branch information
rschu1ze committed Jul 26, 2023
1 parent d4737ca commit aa25ce9
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 76 deletions.
67 changes: 54 additions & 13 deletions docs/en/operations/configuration-files.md
Expand Up @@ -67,27 +67,30 @@ Substitutions can also be performed from ZooKeeper. To do this, specify the attr

## Encrypting Configuration {#encryption}

You can use symmetric encryption to encrypt a configuration element, for example, a password field. To do so, first configure the [encryption codec](../sql-reference/statements/create/table.md#encryption-codecs), then add attribute `encryption_codec` with the name of the encryption codec as value to the element to encrypt.
You can use symmetric encryption to encrypt a configuration element, for example, a password field. To do so, first configure the [encryption codec](../sql-reference/statements/create/table.md#encryption-codecs), then add attribute `encrypted_by` with the name of the encryption codec as value to the element to encrypt.

Unlike attributes `from_zk`, `from_env` and `incl` (or element `include`), no substitution, i.e. decryption of the encrypted value, is performed in the preprocessed file. Decryption happens only at runtime in the server process.

Example:

```xml
<clickhouse>

<encryption_codecs>
<aes_128_gcm_siv>
<key_hex>00112233445566778899aabbccddeeff</key_hex>
</aes_128_gcm_siv>
</encryption_codecs>

<interserver_http_credentials>
<user>admin</user>
<password encryption_codec="AES_128_GCM_SIV">961F000000040000000000EEDDEF4F453CFE6457C4234BD7C09258BD651D85</password>
<password encrypted_by="AES_128_GCM_SIV">961F000000040000000000EEDDEF4F453CFE6457C4234BD7C09258BD651D85</password>
</interserver_http_credentials>

</clickhouse>
```

To get the encrypted value `encrypt_decrypt` example application may be used.
To encrypt a value, you can use the (example) program `encrypt_decrypt`:

Example:

Expand Down Expand Up @@ -138,20 +141,34 @@ Here you can see default config written in YAML: [config.yaml.example](https://g

There are some differences between YAML and XML formats in terms of ClickHouse configurations. Here are some tips for writing a configuration in YAML format.

You should use a Scalar node to write a key-value pair:
An XML tag with a text value is represented by a YAML key-value pair
``` yaml
key: value
```

To create a node, containing other nodes you should use a Map:
Corresponding XML:
``` xml
<key>value</value>
```

A nested XML node is represented by a YAML map:
``` yaml
map_key:
key1: val1
key2: val2
key3: val3
```

To create a list of values or nodes assigned to one tag you should use a Sequence:
Corresponding XML:
``` xml
<map_key>
<key1>val1</key1>
<key2>val2</key2>
<key3>val3</key3>
</map_key>
```

To create the same XML tag multiple times, use a YAML sequence:
``` yaml
seq_key:
- val1
Expand All @@ -162,25 +179,37 @@ seq_key:
key3: val5
```

If you want to write an attribute for a Sequence or Map node, you should use a @ prefix before the attribute key. Note, that @ is reserved by YAML standard, so you should also to wrap it into double quotes:
Corresponding XML:
```xml
<seq_key>val1</seq_key>
<seq_key>val2</seq_key>
<seq_key>
<key1>val3</key1>
</seq_key>
<seq_key>
<map>
<key2>val4</key2>
<key3>val5</key3>
</map>
</seq_key>
```

To provide an XML attribute, you can use an attribute key with a `@` prefix. Note that `@` is reserved by YAML standard, so must be wrapped in double quotes:
``` yaml
map:
"@attr1": value1
"@attr2": value2
key: 123
```

From that Map we will get these XML nodes:

Corresponding XML:
``` xml
<map attr1="value1" attr2="value2">
<key>123</key>
</map>
```

You can also set attributes for Sequence:

It is also possible to use attributes in YAML sequence:
``` yaml
seq:
- "@attr1": value1
Expand All @@ -189,13 +218,25 @@ seq:
- abc
```

So, we can get YAML config equal to this XML one:

Corresponding XML:
``` xml
<seq attr1="value1" attr2="value2">123</seq>
<seq attr1="value1" attr2="value2">abc</seq>
```

The aforementioned syntax does not allow to express XML text nodes with XML attributes as YAML. This special case can be achieved using an
`#text` attribute key:
```yaml
map_key:
"@attr1": value1
"#text": value2
```

Corresponding XML:
```xml
<map_key attr1="value1">value2</map>
```

## Implementation Details {#implementation-details}

For each config file, the server also generates `file-preprocessed.xml` files when starting. These files contain all the completed substitutions and overrides, and they are intended for informational use. If ZooKeeper substitutions were used in the config files but ZooKeeper is not available on the server start, the server loads the configuration from the preprocessed file.
Expand Down
7 changes: 5 additions & 2 deletions docs/ru/operations/configuration-files.md
Expand Up @@ -87,23 +87,26 @@ $ cat /etc/clickhouse-server/users.d/alice.xml

## Шифрование {#encryption}

Вы можете использовать симметричное шифрование для зашифровки элемента конфигурации, например, поля password. Чтобы это сделать, сначала настройте [кодек шифрования](../sql-reference/statements/create/table.md#encryption-codecs), затем добавьте аттибут`encryption_codec` с именем кодека шифрования как значение к элементу, который надо зашифровать.
Вы можете использовать симметричное шифрование для зашифровки элемента конфигурации, например, поля password. Чтобы это сделать, сначала настройте [кодек шифрования](../sql-reference/statements/create/table.md#encryption-codecs), затем добавьте аттибут`encrypted_by` с именем кодека шифрования как значение к элементу, который надо зашифровать.

В отличии от аттрибутов `from_zk`, `from_env` и `incl` (или элемента `include`), подстановка, т.е. расшифровка зашифрованного значения, не выподняется в файле предобработки. Расшифровка происходит только во время исполнения в серверном процессе.

Пример:

```xml
<clickhouse>

<encryption_codecs>
<aes_128_gcm_siv>
<key_hex>00112233445566778899aabbccddeeff</key_hex>
</aes_128_gcm_siv>
</encryption_codecs>

<interserver_http_credentials>
<user>admin</user>
<password encryption_codec="AES_128_GCM_SIV">961F000000040000000000EEDDEF4F453CFE6457C4234BD7C09258BD651D85</password>
<password encrypted_by="AES_128_GCM_SIV">961F000000040000000000EEDDEF4F453CFE6457C4234BD7C09258BD651D85</password>
</interserver_http_credentials>

</clickhouse>
```

Expand Down
20 changes: 10 additions & 10 deletions src/Common/Config/ConfigProcessor.cpp
Expand Up @@ -192,22 +192,22 @@ static void mergeAttributes(Element & config_element, Element & with_element)

std::string ConfigProcessor::encryptValue(const std::string & codec_name, const std::string & value)
{
EncryptionMethod method = getEncryptionMethod(codec_name);
CompressionCodecEncrypted codec(method);
EncryptionMethod encryption_method = toEncryptionMethod(codec_name);
CompressionCodecEncrypted codec(encryption_method);

Memory<> memory;
memory.resize(codec.getCompressedReserveSize(static_cast<UInt32>(value.size())));
auto bytes_written = codec.compress(value.data(), static_cast<UInt32>(value.size()), memory.data());
auto encrypted_value = std::string(memory.data(), bytes_written);
std::string encrypted_value(memory.data(), bytes_written);
std::string hex_value;
boost::algorithm::hex(encrypted_value.begin(), encrypted_value.end(), std::back_inserter(hex_value));
return hex_value;
}

std::string ConfigProcessor::decryptValue(const std::string & codec_name, const std::string & value)
{
EncryptionMethod method = getEncryptionMethod(codec_name);
CompressionCodecEncrypted codec(method);
EncryptionMethod encryption_method = toEncryptionMethod(codec_name);
CompressionCodecEncrypted codec(encryption_method);

Memory<> memory;
std::string encrypted_value;
Expand All @@ -223,7 +223,7 @@ std::string ConfigProcessor::decryptValue(const std::string & codec_name, const

memory.resize(codec.readDecompressedBlockSize(encrypted_value.data()));
codec.decompress(encrypted_value.data(), static_cast<UInt32>(encrypted_value.size()), memory.data());
std::string decrypted_value = std::string(memory.data(), memory.size());
std::string decrypted_value(memory.data(), memory.size());
return decrypted_value;
}

Expand All @@ -234,7 +234,7 @@ void ConfigProcessor::decryptRecursive(Poco::XML::Node * config_root)
if (node->nodeType() == Node::ELEMENT_NODE)
{
Element & element = dynamic_cast<Element &>(*node);
if (element.hasAttribute("encryption_codec"))
if (element.hasAttribute("encrypted_by"))
{
const NodeListPtr children = element.childNodes();
if (children->length() != 1)
Expand All @@ -244,8 +244,8 @@ void ConfigProcessor::decryptRecursive(Poco::XML::Node * config_root)
if (text_node->nodeType() != Node::TEXT_NODE)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Encrypted node {} should have text node", node->nodeName());

auto encryption_codec = element.getAttribute("encryption_codec");
text_node->setNodeValue(decryptValue(encryption_codec, text_node->getNodeValue()));
auto encrypted_by = element.getAttribute("encrypted_by");
text_node->setNodeValue(decryptValue(encrypted_by, text_node->getNodeValue()));
}
decryptRecursive(node);
}
Expand Down Expand Up @@ -775,7 +775,7 @@ ConfigProcessor::LoadedConfig ConfigProcessor::loadConfigWithZooKeeperIncludes(

void ConfigProcessor::decryptEncryptedElements(LoadedConfig & loaded_config)
{
CompressionCodecEncrypted::Configuration::instance().tryLoad(*loaded_config.configuration, "encryption_codecs");
CompressionCodecEncrypted::Configuration::instance().load(*loaded_config.configuration, "encryption_codecs");
Node * config_root = getRootNode(loaded_config.preprocessed_xml.get());
decryptRecursive(config_root);
loaded_config.configuration = new Poco::Util::XMLConfiguration(loaded_config.preprocessed_xml);
Expand Down
4 changes: 2 additions & 2 deletions src/Common/examples/encrypt_decrypt.cpp
Expand Up @@ -3,7 +3,7 @@
#include <Compression/CompressionCodecEncrypted.h>
#include <iostream>

/** This test program encrypts or decrypts text values using a symmetric encryption codec like AES_128_GCM_SIV or AES_256_GCM_SIV.
/** This program encrypts or decrypts text values using a symmetric encryption codec like AES_128_GCM_SIV or AES_256_GCM_SIV.
* Keys for codecs are loaded from <encryption_codecs> section of configuration file.
*
* How to use:
Expand Down Expand Up @@ -32,7 +32,7 @@ int main(int argc, char ** argv)

DB::ConfigProcessor processor(argv[1], false, true);
auto loaded_config = processor.loadConfig();
DB::CompressionCodecEncrypted::Configuration::instance().tryLoad(*loaded_config.configuration, "encryption_codecs");
DB::CompressionCodecEncrypted::Configuration::instance().load(*loaded_config.configuration, "encryption_codecs");

if (action == "-e")
std::cout << processor.encryptValue(codec_name, value) << std::endl;
Expand Down
42 changes: 6 additions & 36 deletions src/Compression/CompressionCodecEncrypted.cpp
Expand Up @@ -31,14 +31,14 @@ namespace ErrorCodes
extern const int BAD_ARGUMENTS;
}

EncryptionMethod getEncryptionMethod(const std::string & name)
EncryptionMethod toEncryptionMethod(const std::string & name)
{
if (name == "AES_128_GCM_SIV")
return AES_128_GCM_SIV;
else if (name == "AES_256_GCM_SIV")
return AES_256_GCM_SIV;
else
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Wrong encryption method. Got {}", name);
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown encryption method. Got {}", name);
}

namespace
Expand All @@ -48,34 +48,22 @@ namespace
String getMethodName(EncryptionMethod Method)
{
if (Method == AES_128_GCM_SIV)
{
return "AES_128_GCM_SIV";
}
else if (Method == AES_256_GCM_SIV)
{
return "AES_256_GCM_SIV";
}
else
{
return "";
}
}

/// Get method code (used for codec, to understand which one we are using)
uint8_t getMethodCode(EncryptionMethod Method)
{
if (Method == AES_128_GCM_SIV)
{
return static_cast<uint8_t>(CompressionMethodByte::AES_128_GCM_SIV);
}
else if (Method == AES_256_GCM_SIV)
{
return static_cast<uint8_t>(CompressionMethodByte::AES_256_GCM_SIV);
}
else
{
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Wrong encryption method. Got {}", getMethodName(Method));
}
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown encryption method. Got {}", getMethodName(Method));
}

} // end of namespace
Expand Down Expand Up @@ -105,17 +93,11 @@ const String empty_nonce = {"\0\0\0\0\0\0\0\0\0\0\0\0", actual_nonce_size};
UInt64 methodKeySize(EncryptionMethod Method)
{
if (Method == AES_128_GCM_SIV)
{
return 16;
}
else if (Method == AES_256_GCM_SIV)
{
return 32;
}
else
{
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Wrong encryption method. Got {}", getMethodName(Method));
}
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown encryption method. Got {}", getMethodName(Method));
}

std::string lastErrorString()
Expand All @@ -130,17 +112,11 @@ std::string lastErrorString()
auto getMethod(EncryptionMethod Method)
{
if (Method == AES_128_GCM_SIV)
{
return EVP_aead_aes_128_gcm_siv;
}
else if (Method == AES_256_GCM_SIV)
{
return EVP_aead_aes_256_gcm_siv;
}
else
{
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Wrong encryption method. Got {}", getMethodName(Method));
}
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown encryption method. Got {}", getMethodName(Method));
}

/// Encrypt plaintext with particular algorithm and put result into ciphertext_and_tag.
Expand Down Expand Up @@ -206,17 +182,11 @@ size_t decrypt(std::string_view ciphertext, char * plaintext, EncryptionMethod m
auto getMethod(EncryptionMethod Method)
{
if (Method == AES_128_GCM_SIV)
{
return EVP_aes_128_gcm;
}
else if (Method == AES_256_GCM_SIV)
{
return EVP_aes_256_gcm;
}
else
{
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Wrong encryption method. Got {}", getMethodName(Method));
}
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown encryption method. Got {}", getMethodName(Method));
}

/// Encrypt plaintext with particular algorithm and put result into ciphertext_and_tag.
Expand Down
4 changes: 2 additions & 2 deletions src/Compression/CompressionCodecEncrypted.h
Expand Up @@ -18,8 +18,8 @@ enum EncryptionMethod
MAX_ENCRYPTION_METHOD
};

/// Get method for string name. Throw exception for wrong name.
EncryptionMethod getEncryptionMethod(const std::string & name);
/// Get encryption method for string name. Throw exception for wrong name.
EncryptionMethod toEncryptionMethod(const std::string & name);

/** This codec encrypts and decrypts blocks with AES-128 in
* GCM-SIV mode (RFC-8452), which is the only cipher currently
Expand Down
7 changes: 5 additions & 2 deletions tests/integration/test_config_decryption/configs/config.xml
@@ -1,4 +1,5 @@
<clickhouse>

<encryption_codecs>
<aes_128_gcm_siv>
<key_hex>00112233445566778899aabbccddeeff</key_hex>
Expand All @@ -7,6 +8,8 @@
<key_hex>00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff</key_hex>
</aes_256_gcm_siv>
</encryption_codecs>
<max_table_size_to_drop encryption_codec="AES_128_GCM_SIV">96260000000B0000000000E8FE3C087CED2205A5071078B29FD5C3B97F824911DED3217E980C</max_table_size_to_drop>
<max_partition_size_to_drop encryption_codec="AES_256_GCM_SIV">97260000000B0000000000BFFF70C4DA718754C1DA0E2F25FF9246D4783F7FFEC4089EC1CC14</max_partition_size_to_drop>

<max_table_size_to_drop encrypted_by="AES_128_GCM_SIV">96260000000B0000000000E8FE3C087CED2205A5071078B29FD5C3B97F824911DED3217E980C</max_table_size_to_drop>
<max_partition_size_to_drop encrypted_by="AES_256_GCM_SIV">97260000000B0000000000BFFF70C4DA718754C1DA0E2F25FF9246D4783F7FFEC4089EC1CC14</max_partition_size_to_drop>

</clickhouse>

0 comments on commit aa25ce9

Please sign in to comment.