diff --git a/lib/dnssec/ta.c b/lib/dnssec/ta.c
index 61e64205c83f5486a11d3fd2bd085fa9ea3c5c43..59e696890f9b037b89592793e89e0767c1b42936 100644
--- a/lib/dnssec/ta.c
+++ b/lib/dnssec/ta.c
@@ -17,9 +17,14 @@
 #include <arpa/inet.h>
 #include <assert.h>
 #include <ctype.h>
+#include <pthread.h>
 
+#include <contrib/ucw/mempool.h>
 #include <libknot/descriptor.h>
+#include <libknot/dname.h>
 #include <libknot/internal/base64.h>
+#include <libknot/rdataset.h>
+#include <libknot/rrset.h>
 #include <libknot/rrtype/rdname.h>
 
 #include "lib/defines.h"
@@ -423,3 +428,222 @@ fail:
 #undef RDATA_MAXSIZE
 #undef SEPARATORS
 }
+
+#define MAX_ANCHORS 16
+struct trust_anchors_nolock {
+	mm_ctx_t pool;
+	knot_rrset_t *anchors[MAX_ANCHORS];
+	int used;
+};
+
+struct trust_anchors {
+	struct trust_anchors_nolock locked;
+	pthread_rwlock_t rwlock;
+};
+
+struct trust_anchors global_trust_anchors = {
+	.locked.pool = {0, },
+	.locked.anchors = {0, },
+	.locked.used = 0,
+};
+
+static int ta_init(struct trust_anchors_nolock *tan)
+{
+	assert(tan);
+
+	memset(tan, 0, sizeof(*tan));
+	tan->pool.ctx = mp_new(4 * CPU_PAGE_SIZE);
+	tan->pool.alloc = (mm_alloc_t) mp_alloc;
+	tan->used = 0;
+
+	return kr_ok();
+}
+
+static void ta_deinit(struct trust_anchors_nolock *tan)
+{
+	assert(tan);
+
+	if (tan->pool.ctx) {
+		mp_delete(tan->pool.ctx);
+		tan->pool.ctx = NULL;
+	}
+}
+
+int kr_ta_init(struct trust_anchors *tas)
+{
+	if (!tas) {
+		return kr_error(EINVAL);
+	}
+
+	int ret = ta_init(&tas->locked);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = pthread_rwlock_init(&tas->rwlock, NULL);
+	if (ret != 0) {
+		ta_deinit(&tas->locked);
+		return kr_error(ret);
+	}
+	return kr_ok();
+}
+
+void kr_ta_deinit(struct trust_anchors *tas)
+{
+	if (!tas) {
+		return;
+	}
+
+	while (pthread_rwlock_destroy(&tas->rwlock) == EBUSY);
+
+	ta_deinit(&tas->locked);
+}
+
+static int ta_reset(struct trust_anchors_nolock *tan, const char *ta_str)
+{
+	assert(tan);
+
+	ta_deinit(tan);
+	int ret = ta_init(tan);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (!ta_str || (ta_str[0] == '\0')) {
+		return kr_ok();
+	}
+
+	knot_rrset_t *ta = NULL;
+	ret = kr_ta_parse(&ta, ta_str, &tan->pool);
+	if (ret != 0) {
+		return ret;
+	}
+
+	assert(ta);
+
+	tan->anchors[tan->used++] = ta;
+
+	return kr_ok();
+}
+
+int kr_ta_reset(struct trust_anchors *tas, const char *ta_str)
+{
+	if (!tas) {
+		return kr_error(ENOENT);
+	}
+
+	int ret = pthread_rwlock_wrlock(&tas->rwlock);
+	if (ret != 0) {
+		return kr_error(ret);
+	}
+
+	ret = ta_reset(&tas->locked, ta_str);
+
+	pthread_rwlock_unlock(&tas->rwlock);
+	return ret;
+}
+
+static knot_rrset_t *ta_find(struct trust_anchors_nolock *tan, const knot_dname_t *name)
+{
+	assert(tan && name);
+
+	knot_rrset_t *found = NULL;
+
+	int i;
+	for (i = 0; i < tan->used; ++i) {
+		if (knot_dname_is_equal(tan->anchors[i]->owner, name)) {
+			found = tan->anchors[i];
+			break;
+		}
+	}
+
+	return found;
+}
+
+static int ta_add(struct trust_anchors_nolock *tan, const char *ta_str)
+{
+	assert(tan && ta_str);
+
+	if (tan->used >= MAX_ANCHORS) {
+		return kr_error(ENOMEM);
+	}
+
+	knot_rrset_t *ta = NULL;
+	int ret = kr_ta_parse(&ta, ta_str, &tan->pool);
+	if (ret != 0) {
+		return ret;
+	}
+	assert(ta);
+
+	knot_rrset_t *found = ta_find(tan, ta->owner);
+	if (!found) {
+		tan->anchors[tan->used++] = ta;
+		return kr_ok();
+	}
+
+	if (found->type != ta->type) {
+		knot_rrset_free(&ta, &tan->pool);
+		return kr_error(EINVAL);
+	}
+
+	ret = knot_rdataset_merge(&found->rrs, &ta->rrs, &tan->pool);
+	knot_rrset_free(&ta, &tan->pool);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return kr_ok();
+}
+
+int kr_ta_add(struct trust_anchors *tas, const char *ta_str)
+{
+	if (!tas || !ta_str) {
+		return kr_error(EINVAL);
+	}
+
+	int ret = pthread_rwlock_wrlock(&tas->rwlock);
+	if (ret != 0) {
+		return kr_error(ret);
+	}
+
+	ret = ta_add(&tas->locked, ta_str);
+
+	pthread_rwlock_unlock(&tas->rwlock);
+	return ret;
+}
+
+static int ta_get(knot_rrset_t **ta, struct trust_anchors_nolock *tan, const knot_dname_t *name, mm_ctx_t *pool)
+{
+	assert(ta && tan && name);
+
+	knot_rrset_t *copy = ta_find(tan, name);
+	if (!copy) {
+		kr_error(ENOENT);
+	}
+
+	copy = knot_rrset_copy(copy, pool);
+	if (!copy) {
+		kr_error(ENOMEM);
+	}
+
+	*ta = copy;
+
+	return kr_ok();
+}
+
+int kr_ta_get(knot_rrset_t **ta, struct trust_anchors *tas, const knot_dname_t *name, mm_ctx_t *pool)
+{
+	if (!ta || !tas || !name) {
+		return kr_error(EINVAL);
+	}
+
+	int ret = pthread_rwlock_rdlock(&tas->rwlock);
+	if (ret != 0) {
+		return kr_error(ret);
+	}
+
+	ret = ta_get(ta, &tas->locked, name, pool);
+
+	pthread_rwlock_unlock(&tas->rwlock);
+	return ret;
+}
diff --git a/lib/dnssec/ta.h b/lib/dnssec/ta.h
index cf525059419723a18d55ed7a53816e81c2640d65..83d89d1f7751afdd90be67dc7397bd1935949f24 100644
--- a/lib/dnssec/ta.h
+++ b/lib/dnssec/ta.h
@@ -20,6 +20,7 @@
 #include <libknot/rrset.h>
 
 //#define ROOT_TA ". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5"
+#define ROOT_NAME ""
 #define ROOT_TA ". IN DS 19036 RSASHA256 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5"
 //#define ROOT_TA ". IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0="
 
@@ -32,3 +33,19 @@
  * @return       0 or an error code
  */
 int kr_ta_parse(knot_rrset_t **rr, const char *ds_str, mm_ctx_t *pool);
+
+/** Trust anchor container structure. */
+struct trust_anchors;
+
+/** Global trust anchor container. */
+extern struct trust_anchors global_trust_anchors;
+
+int kr_ta_init(struct trust_anchors *tas);
+
+void kr_ta_deinit(struct trust_anchors *tas);
+
+int kr_ta_reset(struct trust_anchors *tas, const char *ta_str);
+
+int kr_ta_add(struct trust_anchors *tas, const char *ta_str);
+
+int kr_ta_get(knot_rrset_t **ta, struct trust_anchors *tas, const knot_dname_t *name, mm_ctx_t *pool);
diff --git a/lib/layer/validate.c b/lib/layer/validate.c
index d746390a057665bfc5e2b0c63b599ee25e24e750..80b32eaab5934eece8d83515f7103cef102b7ccd 100644
--- a/lib/layer/validate.c
+++ b/lib/layer/validate.c
@@ -440,7 +440,43 @@ const knot_layer_api_t *validate_layer(struct kr_module *module)
 		.begin = &begin,
 		.consume = &validate,
 	};
+	/* Store module reference */
 	return &_layer;
 }
 
+int validate_init(struct kr_module *module)
+{
+	int ret = kr_ta_init(&global_trust_anchors);
+	if (ret != 0) {
+		return ret;
+	}
+	/* Add root trust anchor. */
+	ret = kr_ta_add(&global_trust_anchors, ROOT_TA);
+	if (ret != 0) {
+		return ret;
+	}
+	return kr_ok();
+}
+
+#warning TODO: set root trust anchor from config
+int validate_config(struct kr_module *module, const char *conf)
+{
+	return kr_ok();
+}
+
+int validate_deinit(struct kr_module *module)
+{
+	kr_ta_deinit(&global_trust_anchors);
+	return kr_ok();
+}
+
+const struct kr_prop validate_prop_list[] = {
+    { NULL, NULL, NULL }
+};
+
+struct kr_prop *validate_props(void)
+{
+	return (struct kr_prop *) validate_prop_list;
+}
+
 KR_MODULE_EXPORT(validate)
diff --git a/lib/module.c b/lib/module.c
index 071394940611722b5cb47b4441e181ed2a8e0097..53cfbe15c892e26e9f912369016c314f6ad3c8a0 100644
--- a/lib/module.c
+++ b/lib/module.c
@@ -25,12 +25,16 @@
 
 /* List of embedded modules */
 const knot_layer_api_t *iterate_layer(struct kr_module *module);
+int validate_init(struct kr_module *module);
+int validate_deinit(struct kr_module *module);
+int validate_config(struct kr_module *module, const char *conf);
 const knot_layer_api_t *validate_layer(struct kr_module *module);
+extern struct kr_prop validate_prop_list[];
 const knot_layer_api_t *rrcache_layer(struct kr_module *module);
 const knot_layer_api_t *pktcache_layer(struct kr_module *module);
 static const struct kr_module embedded_modules[] = {
 	{ "iterate",  NULL, NULL, NULL, iterate_layer, NULL, NULL, NULL },
-	{ "validate", NULL, NULL, NULL, validate_layer, NULL, NULL, NULL },
+	{ "validate", validate_init, validate_deinit, validate_config, validate_layer, validate_prop_list, NULL, NULL },
 	{ "rrcache",  NULL, NULL, NULL, rrcache_layer, NULL, NULL, NULL },
 	{ "pktcache", NULL, NULL, NULL, pktcache_layer, NULL, NULL, NULL },
 };
diff --git a/lib/zonecut.c b/lib/zonecut.c
index 5265936c1be2e2f0998c4eb93b4f01431d3c4ee6..c0bb394a99169954fa113e42d725af370749a4f1 100644
--- a/lib/zonecut.c
+++ b/lib/zonecut.c
@@ -289,10 +289,12 @@ int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut)
 		}
 	}
 
-#warning TODO: set root trust anchor from config
 	/* Set trust anchor. */
 	knot_rrset_free(&cut->trust_anchor, cut->pool);
-	int ret = kr_ta_parse(&cut->trust_anchor, ROOT_TA, cut->pool);
+	/* The root trust anchor can be changed via the validator layer
+	 * (interactive) interface.
+	 */
+	int ret = kr_ta_get(&cut->trust_anchor, &global_trust_anchors, ROOT_NAME, cut->pool);
 	if (ret != 0) {
 		return ret;
 	}