summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md4
-rw-r--r--.github/reviewers.yml35
-rw-r--r--.github/workflows/auto-author-assign.yml4
-rw-r--r--data/templates/container/registries.conf.j227
-rw-r--r--data/templates/container/storage.conf.j24
-rw-r--r--data/templates/container/systemd-unit.j217
-rw-r--r--data/templates/router-advert/radvd.conf.tmpl7
-rw-r--r--data/templates/squid/sg_acl.conf.tmpl1
-rw-r--r--data/templates/squid/squid.conf.tmpl55
-rw-r--r--data/templates/squid/squidGuard.conf.tmpl213
-rw-r--r--data/templates/system/ssh_config.tmpl7
-rw-r--r--debian/control3
-rwxr-xr-xdebian/vyos-1x-smoketest.postinst10
-rw-r--r--interface-definitions/container.xml.in324
-rw-r--r--interface-definitions/firewall-options.xml.in24
-rw-r--r--interface-definitions/include/interface/inbound-interface.xml.i10
-rw-r--r--interface-definitions/interfaces-dummy.xml.in19
-rw-r--r--interface-definitions/policy-local-route.xml.in125
-rw-r--r--interface-definitions/service_router-advert.xml.in13
-rw-r--r--interface-definitions/service_webproxy.xml.in1
-rw-r--r--interface-definitions/system-option.xml.in1
-rw-r--r--op-mode-definitions/container.xml.in176
-rw-r--r--op-mode-definitions/show-interfaces-bonding.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-bridge.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-dummy.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-ethernet.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-geneve.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-input.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-l2tpv3.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-loopback.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-pppoe.xml.in2
-rw-r--r--op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-tunnel.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-vti.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-vxlan.xml.in4
-rw-r--r--op-mode-definitions/show-interfaces-wireguard.xml.in2
-rw-r--r--op-mode-definitions/show-interfaces-wireless.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-wwan.xml.in2
-rw-r--r--python/vyos/base.py44
-rw-r--r--python/vyos/util.py26
-rwxr-xr-xsmoketest/scripts/cli/test_container.py114
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_dummy.py1
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py458
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py36
-rwxr-xr-xsmoketest/scripts/cli/test_service_webproxy.py4
-rwxr-xr-xsrc/conf_mode/container.py395
-rwxr-xr-xsrc/conf_mode/firewall_options.py14
-rwxr-xr-xsrc/conf_mode/policy-local-route.py194
-rwxr-xr-xsrc/conf_mode/service_webproxy.py104
-rwxr-xr-xsrc/conf_mode/system-option.py17
-rwxr-xr-xsrc/migration-scripts/container/0-to-177
-rwxr-xr-xsrc/op_mode/container.py84
-rwxr-xr-xsrc/op_mode/show_ipsec_connections.py284
-rwxr-xr-xsrc/op_mode/webproxy_update_blacklist.sh29
54 files changed, 2798 insertions, 233 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a86ed924a..61ee1d9ff 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,5 +1,5 @@
<!-- All PR should follow this template to allow a clean and transparent review -->
-<!-- Text placed between these delimiters is considered a commend and is not rendered -->
+<!-- Text placed between these delimiters is considered a comment and is not rendered -->
## Change Summary
<!--- Provide a general summary of your changes in the Title above -->
@@ -31,7 +31,7 @@ the box, please use [x]
<!---
Please describe in detail how you tested your changes. Include details of your testing
environment, and the tests you ran. When pasting configs, logs, shell output, backtraces,
-and other large chunks of text, surround this text with triple backticks
+and other large chunks of text, surround this text with triple backtics
```
like this
```
diff --git a/.github/reviewers.yml b/.github/reviewers.yml
index 9ef3ec961..a1647d20d 100644
--- a/.github/reviewers.yml
+++ b/.github/reviewers.yml
@@ -1,34 +1,3 @@
---
-python/**:
- - c-po
- - dmbaturin
- - jestabro
-
-interface-definitions/**:
- - c-po
- - DmitriyEshenko
- - dmbaturin
- - jestabro
- - sever-sever
- - zdc
-
-op-mode-definitions/**:
- - c-po
- - DmitriyEshenko
- - dmbaturin
- - jestabro
- - sever-sever
- - zdc
-
-src/**:
- - c-po
- - DmitriyEshenko
- - dmbaturin
- - jestabro
- - sever-sever
- - zdc
-
-.github/**:
- - c-po
- - dmbaturin
- - UnicronNL
+"**/*":
+ - team: reviewers
diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml
index 81134206b..13bfd9bb1 100644
--- a/.github/workflows/auto-author-assign.yml
+++ b/.github/workflows/auto-author-assign.yml
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Request review based on files changes and/or groups the author belongs to
- uses: shufo/auto-assign-reviewer-by-files@v1.1.1
+ uses: shufo/auto-assign-reviewer-by-files@v1.1.4
with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ token: ${{ secrets.PR_ACTION_ASSIGN_REVIEWERS }}
config: .github/reviewers.yml
diff --git a/data/templates/container/registries.conf.j2 b/data/templates/container/registries.conf.j2
new file mode 100644
index 000000000..c583e0ad5
--- /dev/null
+++ b/data/templates/container/registries.conf.j2
@@ -0,0 +1,27 @@
+### Autogenerated by container.py ###
+
+# For more information on this configuration file, see containers-registries.conf(5).
+#
+# NOTE: RISK OF USING UNQUALIFIED IMAGE NAMES
+# We recommend always using fully qualified image names including the registry
+# server (full dns name), namespace, image name, and tag
+# (e.g., registry.redhat.io/ubi8/ubi:latest). Pulling by digest (i.e.,
+# quay.io/repository/name@digest) further eliminates the ambiguity of tags.
+# When using short names, there is always an inherent risk that the image being
+# pulled could be spoofed. For example, a user wants to pull an image named
+# `foobar` from a registry and expects it to come from myregistry.com. If
+# myregistry.com is not first in the search list, an attacker could place a
+# different `foobar` image at a registry earlier in the search list. The user
+# would accidentally pull and run the attacker's image and code rather than the
+# intended content. We recommend only adding registries which are completely
+# trusted (i.e., registries which don't allow unknown or anonymous users to
+# create accounts with arbitrary names). This will prevent an image from being
+# spoofed, squatted or otherwise made insecure. If it is necessary to use one
+# of these registries, it should be added at the end of the list.
+#
+# An array of host[:port] registries to try when pulling an unqualified image, in order.
+# unqualified-search-registries = ["example.com"]
+
+{% if registry is defined and registry is not none %}
+unqualified-search-registries = {{ registry }}
+{% endif %}
diff --git a/data/templates/container/storage.conf.j2 b/data/templates/container/storage.conf.j2
new file mode 100644
index 000000000..39a072c70
--- /dev/null
+++ b/data/templates/container/storage.conf.j2
@@ -0,0 +1,4 @@
+### Autogenerated by container.py ###
+[storage]
+ driver = "overlay2"
+ graphroot = "/usr/lib/live/mount/persistence/container/storage"
diff --git a/data/templates/container/systemd-unit.j2 b/data/templates/container/systemd-unit.j2
new file mode 100644
index 000000000..fa48384ab
--- /dev/null
+++ b/data/templates/container/systemd-unit.j2
@@ -0,0 +1,17 @@
+### Autogenerated by container.py ###
+[Unit]
+Description=VyOS Container {{ name }}
+
+[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
+Restart=on-failure
+ExecStartPre=/bin/rm -f %t/%n.pid %t/%n.cid
+ExecStart=/usr/bin/podman run \
+ --conmon-pidfile %t/%n.pid --cidfile %t/%n.cid --cgroups=no-conmon \
+ {{ run_args }}
+ExecStop=/usr/bin/podman stop --ignore --cidfile %t/%n.cid -t 5
+ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/%n.cid
+ExecStopPost=/bin/rm -f %t/%n.cid
+PIDFile=%t/%n.pid
+KillMode=none
+Type=forking
diff --git a/data/templates/router-advert/radvd.conf.tmpl b/data/templates/router-advert/radvd.conf.tmpl
index 88d066491..4be6797ee 100644
--- a/data/templates/router-advert/radvd.conf.tmpl
+++ b/data/templates/router-advert/radvd.conf.tmpl
@@ -43,6 +43,13 @@ interface {{ iface }} {
};
{% endfor %}
{% endif %}
+{% if iface_config.source_address is defined %}
+ AdvRASrcAddress {
+{% for source_address in iface_config.source_address %}
+ {{ source_address }};
+{% endfor %}
+ };
+{% endif %}
{% if iface_config.prefix is defined and iface_config.prefix is not none %}
{% for prefix, prefix_options in iface_config.prefix.items() %}
prefix {{ prefix }} {
diff --git a/data/templates/squid/sg_acl.conf.tmpl b/data/templates/squid/sg_acl.conf.tmpl
index ce72b173a..78297a2b8 100644
--- a/data/templates/squid/sg_acl.conf.tmpl
+++ b/data/templates/squid/sg_acl.conf.tmpl
@@ -1,6 +1,5 @@
### generated by service_webproxy.py ###
dbhome {{ squidguard_db_dir }}
-
dest {{ category }}-{{ rule }} {
{% if list_type == 'domains' %}
domainlist {{ category }}/domains
diff --git a/data/templates/squid/squid.conf.tmpl b/data/templates/squid/squid.conf.tmpl
index 8754e762d..88870d5a9 100644
--- a/data/templates/squid/squid.conf.tmpl
+++ b/data/templates/squid/squid.conf.tmpl
@@ -16,25 +16,30 @@ acl Safe_ports port 488 # gss-http
acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http
acl CONNECT method CONNECT
-
+{% if domain_block is defined and domain_block is not none %}
+{% for domain in domain_block %}
+acl BLOCKDOMAIN dstdomain {{ domain }}
+{% endfor %}
+http_access deny BLOCKDOMAIN
+{% endif %}
{% if authentication is defined and authentication is not none %}
-{% if authentication.children is defined and authentication.children is not none %}
+{% if authentication.children is defined and authentication.children is not none %}
auth_param basic children {{ authentication.children }}
-{% endif %}
-{% if authentication.credentials_ttl is defined and authentication.credentials_ttl is not none %}
+{% endif %}
+{% if authentication.credentials_ttl is defined and authentication.credentials_ttl is not none %}
auth_param basic credentialsttl {{ authentication.credentials_ttl }} minute
-{% endif %}
-{% if authentication.realm is defined and authentication.realm is not none %}
+{% endif %}
+{% if authentication.realm is defined and authentication.realm is not none %}
auth_param basic realm "{{ authentication.realm }}"
-{% endif %}
+{% endif %}
{# LDAP based Authentication #}
-{% if authentication.method is defined and authentication.method is not none %}
-{% if authentication.ldap is defined and authentication.ldap is not none and authentication.method == 'ldap' %}
+{% if authentication.method is defined and authentication.method is not none %}
+{% if authentication.ldap is defined and authentication.ldap is not none and authentication.method == 'ldap' %}
auth_param basic program /usr/lib/squid/basic_ldap_auth -v {{ authentication.ldap.version }} -b "{{ authentication.ldap.base_dn }}" {{ '-D "' + authentication.ldap.bind_dn + '"' if authentication.ldap.bind_dn is defined }} {{ '-w "' + authentication.ldap.password + '"' if authentication.ldap.password is defined }} {{ '-f "' + authentication.ldap.filter_expression + '"' if authentication.ldap.filter_expression is defined }} {{ '-u "' + authentication.ldap.username_attribute + '"' if authentication.ldap.username_attribute is defined }} -p {{ authentication.ldap.port }} {{ '-ZZ' if authentication.ldap.use_ssl is defined }} -R -h "{{ authentication.ldap.server }}"
-{% endif %}
+{% endif %}
acl auth proxy_auth REQUIRED
http_access allow auth
-{% endif %}
+{% endif %}
{% endif %}
http_access allow manager localhost
@@ -46,18 +51,18 @@ http_access allow net
http_access deny all
{% if reply_block_mime is defined and reply_block_mime is not none %}
-{% for mime_type in reply_block_mime %}
+{% for mime_type in reply_block_mime %}
acl BLOCK_MIME rep_mime_type {{ mime_type }}
-{% endfor %}
+{% endfor %}
http_reply_access deny BLOCK_MIME
{% endif %}
{% if cache_size is defined and cache_size is not none %}
-{% if cache_size | int > 0 %}
+{% if cache_size | int > 0 %}
cache_dir ufs /var/spool/squid {{ cache_size }} 16 256
-{% else %}
+{% else %}
# disabling disk cache
-{% endif %}
+{% endif %}
{% endif %}
{% if mem_cache_size is defined and mem_cache_size is not none %}
cache_mem {{ mem_cache_size }} MB
@@ -89,9 +94,9 @@ tcp_outgoing_address {{ outgoing_address }}
{% if listen_address is defined and listen_address is not none %}
-{% for address, config in listen_address.items() %}
+{% for address, config in listen_address.items() %}
http_port {{ address }}:{{ config.port if config.port is defined else default_port }} {{ 'intercept' if config.disable_transparent is not defined }}
-{% endfor %}
+{% endfor %}
{% endif %}
http_port 127.0.0.1:{{ default_port }}
@@ -100,16 +105,16 @@ forwarded_for off
{# SquidGuard #}
{% if url_filtering is defined and url_filtering.disable is not defined %}
-{% if url_filtering.squidguard is defined and url_filtering.squidguard is not none %}
-redirect_program /usr/bin/squidGuard -c {{ squidguard_conf }}
-redirect_children 8
-redirector_bypass on
-{% endif %}
+{% if url_filtering.squidguard is defined and url_filtering.squidguard is not none %}
+url_rewrite_program /usr/bin/squidGuard -c {{ squidguard_conf }}
+url_rewrite_children 8
+url_rewrite_bypass on
+{% endif %}
{% endif %}
{% if cache_peer is defined and cache_peer is not none %}
-{% for peer, config in cache_peer.items() %}
+{% for peer, config in cache_peer.items() %}
cache_peer {{ config.address }} {{ config.type }} {{ config.http_port }} {{ config.icp_port }} {{ config.options }}
-{% endfor %}
+{% endfor %}
never_direct allow all
{% endif %}
diff --git a/data/templates/squid/squidGuard.conf.tmpl b/data/templates/squid/squidGuard.conf.tmpl
index f530d1072..47bc8ee75 100644
--- a/data/templates/squid/squidGuard.conf.tmpl
+++ b/data/templates/squid/squidGuard.conf.tmpl
@@ -1,24 +1,31 @@
### generated by service_webproxy.py ###
-{% macro sg_rule(category, log, db_dir) %}
-{% set expressions = db_dir + '/' + category + '/expressions' %}
-dest {{ category }}-default {
+{% macro sg_rule(category, rule, log, db_dir) %}
+{% set domains = db_dir + '/' + category + '/domains' %}
+{% set urls = db_dir + '/' + category + '/urls' %}
+{% set expressions = db_dir + '/' + category + '/expressions' %}
+dest {{ category }}-{{ rule }}{
+{% if domains | is_file %}
domainlist {{ category }}/domains
+{% endif %}
+{% if urls | is_file %}
urllist {{ category }}/urls
-{% if expressions | is_file %}
+{% endif %}
+{% if expressions | is_file %}
expressionlist {{ category }}/expressions
-{% endif %}
-{% if log is defined %}
+{% endif %}
+{% if log is defined %}
log blacklist.log
-{% endif %}
+{% endif %}
}
{% endmacro %}
{% if url_filtering is defined and url_filtering.disable is not defined %}
-{% if url_filtering.squidguard is defined and url_filtering.squidguard is not none %}
-{% set sg_config = url_filtering.squidguard %}
-{% set acl = namespace(value='local-ok-default') %}
-{% set acl.value = acl.value + ' !in-addr' if sg_config.allow_ipaddr_url is not defined else acl.value %}
+{% if url_filtering.squidguard is defined and url_filtering.squidguard is not none %}
+{% set sg_config = url_filtering.squidguard %}
+{% set acl = namespace(value='') %}
+{% set acl.value = acl.value + ' !in-addr' if sg_config.allow_ipaddr_url is not defined else acl.value %}
+{% set ruleacls = {} %}
dbhome {{ squidguard_db_dir }}
logdir /var/log/squid
@@ -32,60 +39,168 @@ rewrite safesearch {
log rewrite.log
}
-{% if sg_config.local_ok is defined and sg_config.local_ok is not none %}
-{% set acl.value = acl.value + ' local-ok-default' %}
+{% if sg_config.local_ok is defined and sg_config.local_ok is not none %}
+{% set acl.value = acl.value + ' local-ok-default' %}
dest local-ok-default {
domainlist local-ok-default/domains
}
-{% endif %}
-{% if sg_config.local_ok_url is defined and sg_config.local_ok_url is not none %}
-{% set acl.value = acl.value + ' local-ok-url-default' %}
+{% endif %}
+
+{% if sg_config.local_ok_url is defined and sg_config.local_ok_url is not none %}
+{% set acl.value = acl.value + ' local-ok-url-default' %}
dest local-ok-url-default {
urllist local-ok-url-default/urls
}
-{% endif %}
-{% if sg_config.local_block is defined and sg_config.local_block is not none %}
-{% set acl.value = acl.value + ' !local-block-default' %}
+{% endif %}
+
+{% if sg_config.local_block is defined and sg_config.local_block is not none %}
+{% set acl.value = acl.value + ' !local-block-default' %}
dest local-block-default {
domainlist local-block-default/domains
}
-{% endif %}
-{% if sg_config.local_block_url is defined and sg_config.local_block_url is not none %}
-{% set acl.value = acl.value + ' !local-block-url-default' %}
+{% endif %}
+
+{% if sg_config.local_block_url is defined and sg_config.local_block_url is not none %}
+{% set acl.value = acl.value + ' !local-block-url-default' %}
dest local-block-url-default {
urllist local-block-url-default/urls
}
-{% endif %}
-{% if sg_config.local_block_keyword is defined and sg_config.local_block_keyword is not none %}
-{% set acl.value = acl.value + ' !local-block-keyword-default' %}
+{% endif %}
+
+{% if sg_config.local_block_keyword is defined and sg_config.local_block_keyword is not none %}
+{% set acl.value = acl.value + ' !local-block-keyword-default' %}
dest local-block-keyword-default {
expressionlist local-block-keyword-default/expressions
}
-{% endif %}
+{% endif %}
+
+{% if sg_config.block_category is defined and sg_config.block_category is not none %}
+{% for category in sg_config.block_category %}
+{{ sg_rule(category, 'default', sg_config.log, squidguard_db_dir) }}
+{% set acl.value = acl.value + ' !' + category + '-default' %}
+{% endfor %}
+{% endif %}
+{% if sg_config.allow_category is defined and sg_config.allow_category is not none %}
+{% for category in sg_config.allow_category %}
+{{ sg_rule(category, 'default', False, squidguard_db_dir) }}
+{% set acl.value = acl.value + ' ' + category + '-default' %}
+{% endfor %}
+{% endif %}
+
+
+{% if sg_config.rule is defined and sg_config.rule is not none %}
+{% for rule, rule_config in sg_config.rule.items() %}
+{% if rule_config.local_ok is defined and rule_config.local_ok is not none %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' local-ok-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'local-ok-' + rule}) %}
+{% endif %}
+dest local-ok-{{ rule }} {
+ domainlist local-ok-{{ rule }}/domains
+}
+{% endif %}
+
+{% if rule_config.local_ok_url is defined and rule_config.local_ok_url is not none %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' local-ok-url-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'local-ok-url-' + rule}) %}
+{% endif %}
+dest local-ok-url-{{ rule }} {
+ urllist local-ok-url-{{ rule }}/urls
+}
+{% endif %}
+
+{% if rule_config.local_block is defined and rule_config.local_block is not none %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !local-block-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!local-block-' + rule}) %}
+{% endif %}
+dest local-block-{{ rule }} {
+ domainlist local-block-{{ rule }}/domains
+}
+{% endif %}
+
+{% if rule_config.local_block_url is defined and rule_config.local_block_url is not none %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !local-block-url-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!ocal-block-url-' + rule}) %}
+{% endif %}
+dest local-block-url-{{ rule }} {
+ urllist local-block-url-{{ rule }}/urls
+}
+{% endif %}
+
+{% if rule_config.local_block_keyword is defined and rule_config.local_block_keyword is not none %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !local-block-keyword-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!local-block-keyword-' + rule}) %}
+{% endif %}
+dest local-block-keyword-{{ rule }} {
+ expressionlist local-block-keyword-{{ rule }}/expressions
+}
+{% endif %}
+
+{% if rule_config.block_category is defined and rule_config.block_category is not none %}
+{% for b_category in rule_config.block_category %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' !' + b_category + '-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:'!' + b_category + '-' + rule}) %}
+{% endif %}
+{{ sg_rule(b_category, rule, sg_config.log, squidguard_db_dir) }}
+{% endfor %}
+{% endif %}
+
+{% if rule_config.allow_category is defined and rule_config.allow_category is not none %}
+{% for a_category in rule_config.allow_category %}
+{% if rule in ruleacls %}
+{% set _dummy = ruleacls.update({rule: ruleacls[rule] + ' ' + a_category + '-' + rule}) %}
+{% else %}
+{% set _dummy = ruleacls.update({rule:a_category + '-' + rule}) %}
+{% endif %}
+{{ sg_rule(a_category, rule, sg_config.log, squidguard_db_dir) }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+
+{% if sg_config.source_group is defined and sg_config.source_group is not none %}
+{% for sgroup, sg_config in sg_config.source_group.items() %}
+{% if sg_config.address is defined and sg_config.address is not none %}
+src {{ sgroup }} {
+{% for address in sg_config.address %}
+ ip {{ address }}
+{% endfor %}
+}
+{% endif %}
+{% endfor %}
+{% endif %}
-{% if sg_config.block_category is defined and sg_config.block_category is not none %}
-{% for category in sg_config.block_category %}
-{{ sg_rule(category, sg_config.log, squidguard_db_dir) }}
-{% set acl.value = acl.value + ' !' + category + '-default' %}
-{% endfor %}
-{% endif %}
-{% if sg_config.allow_category is defined and sg_config.allow_category is not none %}
-{% for category in sg_config.allow_category %}
-{{ sg_rule(category, False, squidguard_db_dir) }}
-{% set acl.value = acl.value + ' ' + category + '-default' %}
-{% endfor %}
-{% endif %}
acl {
- default {
-{% if sg_config.enable_safe_search is defined %}
- rewrite safesearch
-{% endif %}
- pass {{ acl.value }} {{ 'none' if sg_config.default_action is defined and sg_config.default_action == 'block' else 'allow' }}
- redirect 302:http://{{ sg_config.redirect_url }}
-{% if sg_config.log is defined and sg_config.log is not none %}
- log blacklist.log
-{% endif %}
- }
+{% if sg_config.rule is defined and sg_config.rule is not none %}
+{% for rule, rule_config in sg_config.rule.items() %}
+ {{ rule_config.source_group }} {
+ pass {{ ruleacls[rule] }} {{ 'none' if rule_config.default_action is defined and rule_config.default_action == 'block' else 'any' }}
+ }
+{% endfor %}
+{% endif %}
+
+ default {
+{% if sg_config.enable_safe_search is defined and sg_config.enable_safe_search is not none %}
+ rewrite safesearch
+{% endif %}
+ pass {{ acl.value }} {{ 'none' if sg_config.default_action is defined and sg_config.default_action == 'block' else 'any' }}
+ redirect 302:http://{{ sg_config.redirect_url }}
+{% if sg_config.log is defined and sg_config.log is not none %}
+ log blacklist.log
+{% endif %}
+ }
}
-{% endif %}
+{% endif %}
{% endif %}
diff --git a/data/templates/system/ssh_config.tmpl b/data/templates/system/ssh_config.tmpl
index abc03f069..94dac9ed3 100644
--- a/data/templates/system/ssh_config.tmpl
+++ b/data/templates/system/ssh_config.tmpl
@@ -1,3 +1,8 @@
-{% if ssh_client is defined and ssh_client.source_address is defined and ssh_client.source_address is not none %}
+{% if ssh_client is defined %}
+{% if ssh_client.source_address is defined and ssh_client.source_address is not none %}
BindAddress {{ ssh_client.source_address }}
+{% endif %}
+{% if ssh_client.source_interface is defined and ssh_client.source_address is not none %}
+BindInterface {{ ssh_client.source_interface }}
+{% endif %}
{% endif %}
diff --git a/debian/control b/debian/control
index 5100f326a..a93c1fdb8 100644
--- a/debian/control
+++ b/debian/control
@@ -93,6 +93,7 @@ Depends:
pciutils,
pdns-recursor,
pmacct (>= 1.6.0),
+ podman,
pppoe,
procps,
python3,
@@ -138,6 +139,7 @@ Depends:
traceroute,
tuned,
udp-broadcast-relay,
+ uidmap,
usb-modeswitch,
usbutils,
vyatta-bash,
@@ -162,5 +164,6 @@ Description: VyOS configuration scripts and data for VMware
Package: vyos-1x-smoketest
Architecture: all
Depends:
+ skopeo,
vyos-1x
Description: VyOS build sanity checking toolkit
diff --git a/debian/vyos-1x-smoketest.postinst b/debian/vyos-1x-smoketest.postinst
new file mode 100755
index 000000000..18612804c
--- /dev/null
+++ b/debian/vyos-1x-smoketest.postinst
@@ -0,0 +1,10 @@
+#!/bin/sh -e
+
+BUSYBOX_TAG="docker.io/library/busybox:stable"
+OUTPUT_PATH="/usr/share/vyos/busybox-stable.tar"
+
+if [[ -f $OUTPUT_PATH ]]; then
+ rm -f $OUTPUT_PATH
+fi
+
+skopeo copy --additional-tag "$BUSYBOX_TAG" "docker://$BUSYBOX_TAG" "docker-archive:/$OUTPUT_PATH"
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
new file mode 100644
index 000000000..4bac305d1
--- /dev/null
+++ b/interface-definitions/container.xml.in
@@ -0,0 +1,324 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="container" owner="${vyos_conf_scripts_dir}/container.py">
+ <properties>
+ <help>Container applications</help>
+ <priority>1280</priority>
+ </properties>
+ <children>
+ <tagNode name="name">
+ <properties>
+ <help>Container name</help>
+ <constraint>
+ <regex>[-a-zA-Z0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Container name must be alphanumeric and can contain hyphens</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="allow-host-networks">
+ <properties>
+ <help>Allow host networks in container</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="cap-add">
+ <properties>
+ <help>Container capabilities/permissions</help>
+ <completionHelp>
+ <list>net-admin net-bind-service net-raw setpcap sys-admin sys-time</list>
+ </completionHelp>
+ <valueHelp>
+ <format>net-admin</format>
+ <description>Network operations (interface, firewall, routing tables)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>net-bind-service</format>
+ <description>Bind a socket to privileged ports (port numbers less than 1024)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>net-raw</format>
+ <description>Permission to create raw network sockets</description>
+ </valueHelp>
+ <valueHelp>
+ <format>setpcap</format>
+ <description>Capability sets (from bounded or inherited set)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sys-admin</format>
+ <description>Administation operations (quotactl, mount, sethostname, setdomainame)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sys-time</format>
+ <description>Permission to set system clock</description>
+ </valueHelp>
+ <constraint>
+ <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-time)</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/generic-description.xml.i>
+ <tagNode name="device">
+ <properties>
+ <help>Add a host device to the container</help>
+ </properties>
+ <children>
+ <leafNode name="source">
+ <properties>
+ <help>Source device (Example: "/dev/x")</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Source device</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination container device (Example: "/dev/x")</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Destination container device</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ #include <include/generic-disable-node.xml.i>
+ <tagNode name="environment">
+ <properties>
+ <help>Add custom environment variables</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Environment variable name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>Set environment option value</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Set environment option value</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="image">
+ <properties>
+ <help>Image name in the hub-registry</help>
+ </properties>
+ </leafNode>
+ <leafNode name="memory">
+ <properties>
+ <help>Memory (RAM) available to this container</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>Unlimited</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-16384</format>
+ <description>Container memory in megabytes (MB)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ <constraintErrorMessage>Container memory must be in range 0 to 16384 MB</constraintErrorMessage>
+ </properties>
+ <defaultValue>512</defaultValue>
+ </leafNode>
+ <leafNode name="shared-memory">
+ <properties>
+ <help>Shared memory available to this container</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>Unlimited</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-8192</format>
+ <description>Container memory in megabytes (MB)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-8192"/>
+ </constraint>
+ <constraintErrorMessage>Container memory must be in range 0 to 8192 MB</constraintErrorMessage>
+ </properties>
+ <defaultValue>64</defaultValue>
+ </leafNode>
+ <tagNode name="network">
+ <properties>
+ <help>Attach user defined network to container</help>
+ <completionHelp>
+ <path>container network</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <!-- PODMAN currently does not support more then one IPv4 or IPv6 address assignments to a container -->
+ <help>Assign static IP address to container</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="port">
+ <properties>
+ <help>Publish port to the container</help>
+ </properties>
+ <children>
+ <leafNode name="source">
+ <properties>
+ <help>Source host port</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Source host port</description>
+ </valueHelp>
+ <valueHelp>
+ <format>start-end</format>
+ <description>Source host port range (e.g. 10025-10030)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="port-range"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination container port</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Destination container port</description>
+ </valueHelp>
+ <valueHelp>
+ <format>start-end</format>
+ <description>Destination container port range (e.g. 10025-10030)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="port-range"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="protocol">
+ <properties>
+ <help>Transport protocol used for port mapping</help>
+ <completionHelp>
+ <list>tcp udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>Use Transmission Control Protocol for given port</description>
+ </valueHelp>
+ <valueHelp>
+ <format>udp</format>
+ <description>Use User Datagram Protocol for given port</description>
+ </valueHelp>
+ <constraint>
+ <regex>(tcp|udp)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>tcp</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="restart">
+ <properties>
+ <help>Restart options for container</help>
+ <completionHelp>
+ <list>no on-failure always</list>
+ </completionHelp>
+ <valueHelp>
+ <format>no</format>
+ <description>Do not restart containers on exit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>on-failure</format>
+ <description>Restart containers when they exit with a non-zero exit code, retrying indefinitely</description>
+ </valueHelp>
+ <valueHelp>
+ <format>always</format>
+ <description>Restart containers when they exit, regardless of status, retrying indefinitely</description>
+ </valueHelp>
+ <constraint>
+ <regex>(no|on-failure|always)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>on-failure</defaultValue>
+ </leafNode>
+ <tagNode name="volume">
+ <properties>
+ <help>Mount a volume into the container</help>
+ </properties>
+ <children>
+ <leafNode name="source">
+ <properties>
+ <help>Source host directory</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Source host directory</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination container directory</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Destination container directory</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="network">
+ <properties>
+ <help>Network name</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]{1,11}</regex>
+ </constraint>
+ <constraintErrorMessage>Network name cannot be longer than 11 characters</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="description">
+ <properties>
+ <help>Network description</help>
+ </properties>
+ </leafNode>
+ <leafNode name="prefix">
+ <properties>
+ <help>Prefix which allocated to that network</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 network prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 network prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="registry">
+ <properties>
+ <help>Registry Name</help>
+ <multi/>
+ </properties>
+ <defaultValue>docker.io quay.io</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/firewall-options.xml.in b/interface-definitions/firewall-options.xml.in
index 8d9225a9a..1bcee2011 100644
--- a/interface-definitions/firewall-options.xml.in
+++ b/interface-definitions/firewall-options.xml.in
@@ -20,24 +20,40 @@
<leafNode name="adjust-mss">
<properties>
<help>Adjust MSS for IPv4 transit packets</help>
+ <completionHelp>
+ <list>clamp-mss-to-pmtu</list>
+ </completionHelp>
<valueHelp>
- <format>500-1460</format>
+ <format>clamp-mss-to-pmtu</format>
+ <description>Automatically sets the MSS to the proper value</description>
+ </valueHelp>
+ <valueHelp>
+ <format>536-65535</format>
<description>TCP Maximum segment size in bytes</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 500-1460"/>
+ <validator name="numeric" argument="--range 536-65535"/>
+ <regex>(clamp-mss-to-pmtu)</regex>
</constraint>
</properties>
</leafNode>
<leafNode name="adjust-mss6">
<properties>
<help>Adjust MSS for IPv6 transit packets</help>
+ <completionHelp>
+ <list>clamp-mss-to-pmtu</list>
+ </completionHelp>
+ <valueHelp>
+ <format>clamp-mss-to-pmtu</format>
+ <description>Automatically sets the MSS to the proper value</description>
+ </valueHelp>
<valueHelp>
- <format>1280-1492</format>
+ <format>1220-65535</format>
<description>TCP Maximum segment size in bytes</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1280-1492"/>
+ <validator name="numeric" argument="--range 1220-65535"/>
+ <regex>(clamp-mss-to-pmtu)</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i
new file mode 100644
index 000000000..5a8d47280
--- /dev/null
+++ b/interface-definitions/include/interface/inbound-interface.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from interface/inbound-interface.xml.i -->
+<leafNode name="inbound-interface">
+ <properties>
+ <help>Inbound Interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in
index 2bc88c1a7..ac1a35cf5 100644
--- a/interface-definitions/interfaces-dummy.xml.in
+++ b/interface-definitions/interfaces-dummy.xml.in
@@ -25,8 +25,27 @@
</properties>
<children>
#include <include/interface/source-validation.xml.i>
+ #include <include/interface/disable-forwarding.xml.i>
</children>
</node>
+ <node name="ipv6">
+ <properties>
+ <help>IPv6 routing parameters</help>
+ </properties>
+ <children>
+ #include <include/interface/disable-forwarding.xml.i>
+ <node name="address">
+ <properties>
+ <help>IPv6 address configuration modes</help>
+ </properties>
+ <children>
+ #include <include/interface/ipv6-address-eui64.xml.i>
+ #include <include/interface/ipv6-address-no-default-link-local.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ #include <include/interface/mtu-68-16000.xml.i>
#include <include/interface/vrf.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in
index 3769c3748..8619e839e 100644
--- a/interface-definitions/policy-local-route.xml.in
+++ b/interface-definitions/policy-local-route.xml.in
@@ -6,6 +6,7 @@
<node name="local-route" owner="${vyos_conf_scripts_dir}/policy-local-route.py">
<properties>
<help>IPv4 policy route of local traffic</help>
+ <priority>500</priority>
</properties>
<children>
<tagNode name="rule">
@@ -14,7 +15,7 @@
<valueHelp>
<!-- table main with prio 32766 -->
<format>u32:1-32765</format>
- <description>Local-route rule number (1-219)</description>
+ <description>Local-route rule number (1-32765)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-32765"/>
@@ -40,6 +41,18 @@
</leafNode>
</children>
</node>
+ <leafNode name="fwmark">
+ <properties>
+ <help>Match fwmark value</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="source">
<properties>
<help>Source address or prefix</help>
@@ -58,6 +71,116 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination address or prefix</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Prefix to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ip-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/interface/inbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="local-route6" owner="${vyos_conf_scripts_dir}/policy-local-route.py">
+ <properties>
+ <help>IPv6 policy route of local traffic</help>
+ <priority>500</priority>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv6 policy local-route rule set number</help>
+ <valueHelp>
+ <!-- table main with prio 32766 -->
+ <format>u32:1-32765</format>
+ <description>Local-route rule number (1-32765)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-32765"/>
+ </constraint>
+ </properties>
+ <children>
+ <node name="set">
+ <properties>
+ <help>Packet modifications</help>
+ </properties>
+ <children>
+ <leafNode name="table">
+ <properties>
+ <help>Routing table to forward packet with</help>
+ <valueHelp>
+ <format>u32:1-200</format>
+ <description>Table number</description>
+ </valueHelp>
+ <completionHelp>
+ <list>main</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="fwmark">
+ <properties>
+ <help>Match fwmark value</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="source">
+ <properties>
+ <help>Source address or prefix</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>Prefix to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination address or prefix</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>Prefix to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/interface/inbound-interface.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in
index 0f4009f5c..a15ce8b8f 100644
--- a/interface-definitions/service_router-advert.xml.in
+++ b/interface-definitions/service_router-advert.xml.in
@@ -276,6 +276,19 @@
</leafNode>
</children>
</tagNode>
+ <leafNode name="source-address">
+ <properties>
+ <help>Use IPv6 address as source address. Useful with VRRP.</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address to be advertized (must be configured on interface)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
<leafNode name="reachable-time">
<properties>
<help>Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation</help>
diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in
index bd5396291..9136e2fe7 100644
--- a/interface-definitions/service_webproxy.xml.in
+++ b/interface-definitions/service_webproxy.xml.in
@@ -513,6 +513,7 @@
<validator name="ipv4-prefix"/>
<validator name="ipv4-range"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
<leafNode name="description">
diff --git a/interface-definitions/system-option.xml.in b/interface-definitions/system-option.xml.in
index 5f80e064d..b47dde0a0 100644
--- a/interface-definitions/system-option.xml.in
+++ b/interface-definitions/system-option.xml.in
@@ -105,6 +105,7 @@
</properties>
<children>
#include <include/source-address-ipv4-ipv6.xml.i>
+ #include <include/source-interface.xml.i>
</children>
</node>
<leafNode name="startup-beep">
diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in
new file mode 100644
index 000000000..786bd66d3
--- /dev/null
+++ b/op-mode-definitions/container.xml.in
@@ -0,0 +1,176 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="add">
+ <children>
+ <node name="container">
+ <properties>
+ <help>Add container image</help>
+ </properties>
+ <children>
+ <tagNode name="image">
+ <properties>
+ <help>Pull a new image for container</help>
+ </properties>
+ <command>sudo podman image pull "${4}"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="connect">
+ <children>
+ <tagNode name="container">
+ <properties>
+ <help>Attach to a running container</help>
+ <completionHelp>
+ <path>container name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo podman exec --interactive --tty "$3" /bin/sh</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="delete">
+ <children>
+ <node name="container">
+ <properties>
+ <help>Delete container image</help>
+ </properties>
+ <children>
+ <tagNode name="image">
+ <properties>
+ <help>Delete container image</help>
+ <completionHelp>
+ <script>sudo podman image ls -q</script>
+ </completionHelp>
+ </properties>
+ <command>sudo podman image rm --force "${4}"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="generate">
+ <children>
+ <node name="container">
+ <properties>
+ <help>Generate Container Image</help>
+ </properties>
+ <children>
+ <tagNode name="image">
+ <properties>
+ <help>Name of container image (tag)</help>
+ </properties>
+ <children>
+ <tagNode name="path">
+ <properties>
+ <help>Path to Dockerfile</help>
+ <completionHelp>
+ <list>&lt;filename&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>sudo podman build --net host --layers --force-rm --tag "$4" $6</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="monitor">
+ <children>
+ <node name="log">
+ <children>
+ <tagNode name="container">
+ <properties>
+ <help>Monitor last lines of container logs</help>
+ <completionHelp>
+ <path>container name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo podman logs --follow --names "$4"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="show">
+ <children>
+ <node name="container">
+ <properties>
+ <help>Show containers</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/container.py show_container</command>
+ <children>
+ <leafNode name="image">
+ <properties>
+ <help>Show container image</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/container.py show_image</command>
+ </leafNode>
+ <tagNode name="log">
+ <properties>
+ <help>Show logs from a given container</help>
+ <completionHelp>
+ <path>container name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo podman logs --names "$4"</command>
+ </tagNode>
+ <leafNode name="network">
+ <properties>
+ <help>Show available container networks</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/container.py show_network</command>
+ </leafNode>
+ </children>
+ </node>
+ <node name="log">
+ <children>
+ <tagNode name="container">
+ <properties>
+ <help>Show logs from a given container</help>
+ <completionHelp>
+ <path>container name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo podman logs --names "$4"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="restart">
+ <children>
+ <tagNode name="container">
+ <properties>
+ <help>Restart a given container</help>
+ <completionHelp>
+ <path>container name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/container.py restart --name="$3"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="update">
+ <children>
+ <node name="container">
+ <properties>
+ <help>Update a container image</help>
+ </properties>
+ <children>
+ <tagNode name="image">
+ <properties>
+ <help>Update container image</help>
+ <completionHelp>
+ <path>container name</path>
+ </completionHelp>
+ </properties>
+ <command>if cli-shell-api existsActive container name "$4"; then sudo podman pull $(cli-shell-api returnActiveValue container name "$4" image); else echo "Container $4 does not exist"; fi</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-interfaces-bonding.xml.in b/op-mode-definitions/show-interfaces-bonding.xml.in
index d4e737d5b..c3cf91992 100644
--- a/op-mode-definitions/show-interfaces-bonding.xml.in
+++ b/op-mode-definitions/show-interfaces-bonding.xml.in
@@ -11,13 +11,13 @@
<path>interfaces bonding</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=bonding</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified bonding interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=bonding</command>
</leafNode>
<leafNode name="detail">
<properties>
@@ -32,13 +32,13 @@
<path>interfaces bonding ${COMP_WORDS[3]} vif</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --intf-type=bonding</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of specified virtual network interface (vif) information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief --intf-type=bonding</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-bridge.xml.in b/op-mode-definitions/show-interfaces-bridge.xml.in
index d4908b341..67b7c3125 100644
--- a/op-mode-definitions/show-interfaces-bridge.xml.in
+++ b/op-mode-definitions/show-interfaces-bridge.xml.in
@@ -11,13 +11,13 @@
<path>interfaces bridge</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=bridge</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified bridge interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=bridge</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-dummy.xml.in b/op-mode-definitions/show-interfaces-dummy.xml.in
index 52d2cc7ee..eb8e4bf80 100644
--- a/op-mode-definitions/show-interfaces-dummy.xml.in
+++ b/op-mode-definitions/show-interfaces-dummy.xml.in
@@ -11,13 +11,13 @@
<path>interfaces dummy</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=dummy</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified dummy interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=dummy</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-ethernet.xml.in b/op-mode-definitions/show-interfaces-ethernet.xml.in
index e414291d1..c1461939c 100644
--- a/op-mode-definitions/show-interfaces-ethernet.xml.in
+++ b/op-mode-definitions/show-interfaces-ethernet.xml.in
@@ -11,13 +11,13 @@
<path>interfaces ethernet</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=ethernet</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified ethernet interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=ethernet</command>
</leafNode>
<leafNode name="identify">
<properties>
@@ -58,13 +58,13 @@
<path>interfaces ethernet ${COMP_WORDS[3]} vif</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --intf-type=ethernet</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of specified virtual network interface (vif) information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief --intf-type=ethernet</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-geneve.xml.in b/op-mode-definitions/show-interfaces-geneve.xml.in
index a47933315..5cb6f6dbf 100644
--- a/op-mode-definitions/show-interfaces-geneve.xml.in
+++ b/op-mode-definitions/show-interfaces-geneve.xml.in
@@ -11,13 +11,13 @@
<path>interfaces geneve</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=geneve</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified GENEVE interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=geneve</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-input.xml.in b/op-mode-definitions/show-interfaces-input.xml.in
index 9ae3828c8..0753195e5 100644
--- a/op-mode-definitions/show-interfaces-input.xml.in
+++ b/op-mode-definitions/show-interfaces-input.xml.in
@@ -11,13 +11,13 @@
<path>interfaces input</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=input</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified input interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=input</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-l2tpv3.xml.in b/op-mode-definitions/show-interfaces-l2tpv3.xml.in
index 2a1d6a1c6..f8483a368 100644
--- a/op-mode-definitions/show-interfaces-l2tpv3.xml.in
+++ b/op-mode-definitions/show-interfaces-l2tpv3.xml.in
@@ -11,13 +11,13 @@
<path>interfaces l2tpv3</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=l2tpv3</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified L2TPv3 interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=l2tpv3</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-loopback.xml.in b/op-mode-definitions/show-interfaces-loopback.xml.in
index 25a75ffff..eb86d5ece 100644
--- a/op-mode-definitions/show-interfaces-loopback.xml.in
+++ b/op-mode-definitions/show-interfaces-loopback.xml.in
@@ -11,13 +11,13 @@
<path>interfaces loopback</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=loopback</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified dummy interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=dummy</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-pppoe.xml.in b/op-mode-definitions/show-interfaces-pppoe.xml.in
index 767836abf..3840cbdca 100644
--- a/op-mode-definitions/show-interfaces-pppoe.xml.in
+++ b/op-mode-definitions/show-interfaces-pppoe.xml.in
@@ -11,7 +11,7 @@
<path>interfaces pppoe</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=pppoe</command>
<children>
<leafNode name="log">
<properties>
diff --git a/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in
index 2ae4b5a9e..add6e67fd 100644
--- a/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in
+++ b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in
@@ -11,13 +11,13 @@
<path>interfaces pseudo-ethernet</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=pseudo-ethernet</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified pseudo-ethernet/MACvlan interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=pseudo-ethernet</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-tunnel.xml.in b/op-mode-definitions/show-interfaces-tunnel.xml.in
index 51b25efd9..b3c1318e9 100644
--- a/op-mode-definitions/show-interfaces-tunnel.xml.in
+++ b/op-mode-definitions/show-interfaces-tunnel.xml.in
@@ -11,13 +11,13 @@
<path>interfaces tunnel</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=tunnel</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified tunnel interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=tunnel</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-vti.xml.in b/op-mode-definitions/show-interfaces-vti.xml.in
index b436b8414..4d193ef59 100644
--- a/op-mode-definitions/show-interfaces-vti.xml.in
+++ b/op-mode-definitions/show-interfaces-vti.xml.in
@@ -11,13 +11,13 @@
<path>interfaces vti</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=vti</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified vti interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=vti</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-vxlan.xml.in b/op-mode-definitions/show-interfaces-vxlan.xml.in
index 1befd428c..6600a312b 100644
--- a/op-mode-definitions/show-interfaces-vxlan.xml.in
+++ b/op-mode-definitions/show-interfaces-vxlan.xml.in
@@ -11,13 +11,13 @@
<path>interfaces vxlan</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=vxlan</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified VXLAN interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=vxlan</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-wireguard.xml.in b/op-mode-definitions/show-interfaces-wireguard.xml.in
index c9b754dcd..c6ca71ab7 100644
--- a/op-mode-definitions/show-interfaces-wireguard.xml.in
+++ b/op-mode-definitions/show-interfaces-wireguard.xml.in
@@ -11,7 +11,7 @@
<script>${vyos_completion_dir}/list_interfaces.py --type wireguard</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=wireguard</command>
<children>
<leafNode name="allowed-ips">
<properties>
diff --git a/op-mode-definitions/show-interfaces-wireless.xml.in b/op-mode-definitions/show-interfaces-wireless.xml.in
index 4a37417aa..9a63f1629 100644
--- a/op-mode-definitions/show-interfaces-wireless.xml.in
+++ b/op-mode-definitions/show-interfaces-wireless.xml.in
@@ -31,13 +31,13 @@
<script>${vyos_completion_dir}/list_interfaces.py --type wireless</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=wireless</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified wireless interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief --intf-type=wireless</command>
</leafNode>
<node name="scan">
<properties>
@@ -63,13 +63,13 @@
<properties>
<help>Show specified virtual network interface (vif) information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --intf-type=wireless</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of specified virtual network interface (vif) information</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief --intf-type=wireless</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in
index 3cd29b38a..013668f68 100644
--- a/op-mode-definitions/show-interfaces-wwan.xml.in
+++ b/op-mode-definitions/show-interfaces-wwan.xml.in
@@ -12,7 +12,7 @@
<script>cd /sys/class/net; ls -d wwan*</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --intf-type=wirelessmodem</command>
<children>
<leafNode name="capabilities">
<properties>
diff --git a/python/vyos/base.py b/python/vyos/base.py
index fd22eaccd..9b93cb2f2 100644
--- a/python/vyos/base.py
+++ b/python/vyos/base.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -15,11 +15,47 @@
from textwrap import fill
+
+class BaseWarning:
+ def __init__(self, header, message, **kwargs):
+ self.message = message
+ self.kwargs = kwargs
+ if 'width' not in kwargs:
+ self.width = 72
+ if 'initial_indent' in kwargs:
+ del self.kwargs['initial_indent']
+ if 'subsequent_indent' in kwargs:
+ del self.kwargs['subsequent_indent']
+ self.textinitindent = header
+ self.standardindent = ''
+
+ def print(self):
+ messages = self.message.split('\n')
+ isfirstmessage = True
+ initial_indent = self.textinitindent
+ print('')
+ for mes in messages:
+ mes = fill(mes, initial_indent=initial_indent,
+ subsequent_indent=self.standardindent, **self.kwargs)
+ if isfirstmessage:
+ isfirstmessage = False
+ initial_indent = self.standardindent
+ print(f'{mes}')
+ print('')
+
+
+class Warning():
+ def __init__(self, message, **kwargs):
+ self.BaseWarn = BaseWarning('WARNING: ', message, **kwargs)
+ self.BaseWarn.print()
+
+
class DeprecationWarning():
- def __init__(self, message):
+ def __init__(self, message, **kwargs):
# Reformat the message and trim it to 72 characters in length
- message = fill(message, width=72)
- print(f'\nDEPRECATION WARNING: {message}\n')
+ self.BaseWarn = BaseWarning('DEPRECATION WARNING: ', message, **kwargs)
+ self.BaseWarn.print()
+
class ConfigError(Exception):
def __init__(self, message):
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 1c4102e90..67ec3ecc6 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -699,6 +699,32 @@ def dict_search(path, dict_object):
c = c.get(p, {})
return c.get(parts[-1], None)
+def convert_data(data):
+ """Convert multiple types of data to types usable in CLI
+
+ Args:
+ data (str | bytes | list | OrderedDict): input data
+
+ Returns:
+ str | list | dict: converted data
+ """
+ from collections import OrderedDict
+
+ if isinstance(data, str):
+ return data
+ if isinstance(data, bytes):
+ return data.decode()
+ if isinstance(data, list):
+ list_tmp = []
+ for item in data:
+ list_tmp.append(convert_data(item))
+ return list_tmp
+ if isinstance(data, OrderedDict):
+ dict_tmp = {}
+ for key, value in data.items():
+ dict_tmp[key] = convert_data(value)
+ return dict_tmp
+
def get_bridge_fdb(interface):
""" Returns the forwarding database entries for a given interface """
if not os.path.exists(f'/sys/class/net/{interface}'):
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
new file mode 100755
index 000000000..49978151c
--- /dev/null
+++ b/smoketest/scripts/cli/test_container.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import glob
+import json
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.util import cmd
+from vyos.util import process_named_running
+from vyos.util import read_file
+
+base_path = ['container']
+cont_image = 'busybox:stable' # busybox is included in vyos-build
+prefix = '192.168.205.0/24'
+net_name = 'NET01'
+PROCESS_NAME = 'conmon'
+PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid'
+
+busybox_image_path = '/usr/share/vyos/busybox-stable.tar'
+
+def cmd_to_json(command):
+ c = cmd(command + ' --format=json')
+ data = json.loads(c)[0]
+
+ return data
+
+
+class TestContainer(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestContainer, cls).setUpClass()
+
+ # Load image for smoketest provided in vyos-build
+ try:
+ cmd(f'cat {busybox_image_path} | sudo podman load')
+ except:
+ cls.skipTest(cls, reason='busybox image not available')
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestContainer, cls).tearDownClass()
+
+ # Cleanup podman image
+ cmd(f'sudo podman image rm -f {cont_image}')
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # Ensure no container process remains
+ self.assertIsNone(process_named_running(PROCESS_NAME))
+
+ # Ensure systemd units are removed
+ units = glob.glob('/run/systemd/system/vyos-container-*')
+ self.assertEqual(units, [])
+
+ def test_01_basic_container(self):
+ cont_name = 'c1'
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '10.0.2.15/24'])
+ self.cli_set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '10.0.2.2'])
+ self.cli_set(['system', 'name-server', '1.1.1.1'])
+ self.cli_set(['system', 'name-server', '8.8.8.8'])
+
+ self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
+ self.cli_set(base_path + ['name', cont_name, 'allow-host-networks'])
+
+ # commit changes
+ self.cli_commit()
+
+ pid = 0
+ with open(PROCESS_PIDFILE.format(cont_name), 'r') as f:
+ pid = int(f.read())
+
+ # Check for running process
+ self.assertEqual(process_named_running(PROCESS_NAME), pid)
+
+ def test_02_container_network(self):
+ cont_name = 'c2'
+ cont_ip = '192.168.205.25'
+ self.cli_set(base_path + ['network', net_name, 'prefix', prefix])
+ self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
+ self.cli_set(base_path + ['name', cont_name, 'network', net_name, 'address', cont_ip])
+
+ # commit changes
+ self.cli_commit()
+
+ n = cmd_to_json(f'sudo podman network inspect {net_name}')
+ json_subnet = n['plugins'][0]['ipam']['ranges'][0][0]['subnet']
+
+ c = cmd_to_json(f'sudo podman container inspect {cont_name}')
+ json_ip = c['NetworkSettings']['Networks'][net_name]['IPAddress']
+
+ self.assertEqual(json_subnet, prefix)
+ self.assertEqual(json_ip, cont_ip)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_dummy.py b/smoketest/scripts/cli/test_interfaces_dummy.py
index dedc6fe05..fb49d6e39 100755
--- a/smoketest/scripts/cli/test_interfaces_dummy.py
+++ b/smoketest/scripts/cli/test_interfaces_dummy.py
@@ -21,6 +21,7 @@ from base_interfaces_test import BasicInterfaceTest
class DummyInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
+ cls._test_mtu = True
cls._base_path = ['interfaces', 'dummy']
cls._interfaces = ['dum435', 'dum8677', 'dum0931', 'dum089']
# call base-classes classmethod
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index f1d195381..ab63cbcf7 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -678,18 +678,464 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- # Check generated configuration
-
- # Expected values
original = """
50: from 203.0.113.1 lookup 23
50: from 203.0.113.2 lookup 23
"""
tmp = cmd('ip rule show prio 50')
- original = original.split()
- tmp = tmp.split()
- self.assertEqual(tmp, original)
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for fwmark
+ def test_fwmark_table_id(self):
+ path = base_path + ['local-route']
+
+ fwmk = '24'
+ rule = '101'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 101: from all fwmark 0x18 lookup 154
+ """
+ tmp = cmd('ip rule show prio 101')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for destination
+ def test_destination_table_id(self):
+ path = base_path + ['local-route']
+
+ dst = '203.0.113.1'
+ rule = '102'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+
+ self.cli_commit()
+
+ original = """
+ 102: from all to 203.0.113.1 lookup 154
+ """
+ tmp = cmd('ip rule show prio 102')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with fwmark
+ def test_fwmark_sources_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['203.0.113.11', '203.0.113.12']
+ fwmk = '23'
+ rule = '100'
+ table = '150'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 100: from 203.0.113.11 fwmark 0x17 lookup 150
+ 100: from 203.0.113.12 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 100')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with iif
+ def test_iif_sources_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['203.0.113.11', '203.0.113.12']
+ iif = 'lo'
+ rule = '100'
+ table = '150'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'inbound-interface', iif])
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'source', src])
+
+ self.cli_commit()
+
+ # Check generated configuration
+ # Expected values
+ original = """
+ 100: from 203.0.113.11 iif lo lookup 150
+ 100: from 203.0.113.12 iif lo lookup 150
+ """
+ tmp = cmd('ip rule show prio 100')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources and destinations with fwmark
+ def test_fwmark_sources_destination_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['203.0.113.11', '203.0.113.12']
+ destinations = ['203.0.113.13', '203.0.113.15']
+ fwmk = '23'
+ rule = '103'
+ table = '150'
+ for src in sources:
+ for dst in destinations:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 103: from 203.0.113.11 to 203.0.113.13 fwmark 0x17 lookup 150
+ 103: from 203.0.113.11 to 203.0.113.15 fwmark 0x17 lookup 150
+ 103: from 203.0.113.12 to 203.0.113.13 fwmark 0x17 lookup 150
+ 103: from 203.0.113.12 to 203.0.113.15 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table ipv6 for some sources ipv6
+ def test_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:123::/48', '2001:db8:126::/48']
+ rule = '50'
+ table = '23'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+
+ self.cli_commit()
+
+ original = """
+ 50: from 2001:db8:123::/48 lookup 23
+ 50: from 2001:db8:126::/48 lookup 23
+ """
+ tmp = cmd('ip -6 rule show prio 50')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for fwmark ipv6
+ def test_fwmark_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ fwmk = '24'
+ rule = '100'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 100: from all fwmark 0x18 lookup 154
+ """
+ tmp = cmd('ip -6 rule show prio 100')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for destination ipv6
+ def test_destination_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ dst = '2001:db8:1337::/126'
+ rule = '101'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+
+ self.cli_commit()
+
+ original = """
+ 101: from all to 2001:db8:1337::/126 lookup 154
+ """
+ tmp = cmd('ip -6 rule show prio 101')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with fwmark ipv6
+ def test_fwmark_sources_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:1338::/126', '2001:db8:1339::/126']
+ fwmk = '23'
+ rule = '102'
+ table = '150'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 102: from 2001:db8:1338::/126 fwmark 0x17 lookup 150
+ 102: from 2001:db8:1339::/126 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip -6 rule show prio 102')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with iif ipv6
+ def test_iif_sources_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:1338::/126', '2001:db8:1339::/126']
+ iif = 'lo'
+ rule = '102'
+ table = '150'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'inbound-interface', iif])
+
+ self.cli_commit()
+
+ original = """
+ 102: from 2001:db8:1338::/126 iif lo lookup 150
+ 102: from 2001:db8:1339::/126 iif lo lookup 150
+ """
+ tmp = cmd('ip -6 rule show prio 102')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources and destinations with fwmark ipv6
+ def test_fwmark_sources_destination_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:1338::/126', '2001:db8:1339::/56']
+ destinations = ['2001:db8:13::/48', '2001:db8:16::/48']
+ fwmk = '23'
+ rule = '103'
+ table = '150'
+ for src in sources:
+ for dst in destinations:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip -6 rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test delete table for sources and destination with fwmark ipv4/ipv6
+ def test_delete_ipv4_ipv6_table_id(self):
+ path = base_path + ['local-route']
+ path_v6 = base_path + ['local-route6']
+
+ sources = ['203.0.113.0/24', '203.0.114.5']
+ destinations = ['203.0.112.0/24', '203.0.116.5']
+ sources_v6 = ['2001:db8:1338::/126', '2001:db8:1339::/56']
+ destinations_v6 = ['2001:db8:13::/48', '2001:db8:16::/48']
+ fwmk = '23'
+ rule = '103'
+ table = '150'
+ for src in sources:
+ for dst in destinations:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ for src in sources_v6:
+ for dst in destinations_v6:
+ self.cli_set(path_v6 + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path_v6 + ['rule', rule, 'source', src])
+ self.cli_set(path_v6 + ['rule', rule, 'destination', dst])
+ self.cli_set(path_v6 + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 103: from 203.0.113.0/24 to 203.0.116.5 fwmark 0x17 lookup 150
+ 103: from 203.0.114.5 to 203.0.112.0/24 fwmark 0x17 lookup 150
+ 103: from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150
+ 103: from 203.0.113.0/24 to 203.0.112.0/24 fwmark 0x17 lookup 150
+ """
+ original_v6 = """
+ 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 103')
+ tmp_v6 = cmd('ip -6 rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+ self.assertEqual(sort_ip(tmp_v6), sort_ip(original_v6))
+
+ self.cli_delete(path)
+ self.cli_delete(path_v6)
+ self.cli_commit()
+
+ tmp = cmd('ip rule show prio 103')
+ tmp_v6 = cmd('ip -6 rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), [])
+ self.assertEqual(sort_ip(tmp_v6), [])
+
+ # Test multiple commits ipv4
+ def test_multiple_commit_ipv4_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['192.0.2.1', '192.0.2.2']
+ destination = '203.0.113.25'
+ rule = '105'
+ table = '151'
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'source', src])
+
+ self.cli_commit()
+
+ original_first = """
+ 105: from 192.0.2.1 lookup 151
+ 105: from 192.0.2.2 lookup 151
+ """
+ tmp = cmd('ip rule show prio 105')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original_first))
+
+ # Create second commit with added destination
+ self.cli_set(path + ['rule', rule, 'destination', destination])
+ self.cli_commit()
+
+ original_second = """
+ 105: from 192.0.2.1 to 203.0.113.25 lookup 151
+ 105: from 192.0.2.2 to 203.0.113.25 lookup 151
+ """
+ tmp = cmd('ip rule show prio 105')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original_second))
+
+ # Test set table for fwmark
+ def test_fwmark_table_id(self):
+ path = base_path + ['local-route']
+
+ fwmk = '24'
+ rule = '101'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 101: from all fwmark 0x18 lookup 154
+ """
+ tmp = cmd('ip rule show prio 101')
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with fwmark
+ def test_fwmark_sources_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['203.0.113.11', '203.0.113.12']
+ fwmk = '23'
+ rule = '100'
+ table = '150'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 100: from 203.0.113.11 fwmark 0x17 lookup 150
+ 100: from 203.0.113.12 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 100')
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test remove fwmark for sources with fwmark
+ def test_source_fwmk_remove(self):
+ path = base_path + ['local-route']
+
+ src = '203.0.113.11'
+ dst = '203.0.113.0/24'
+ fwmk = '23'
+ rule = '100'
+ table = '150'
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 100: from 203.0.113.11 to 203.0.113.0/24 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 100')
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ self.cli_delete(path + ['rule', rule, 'source', src])
+ self.cli_commit()
+
+ original = """
+ 100: from all to 203.0.113.0/24 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 100')
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test change table for sources with fwmark
+ def test_source_change_table(self):
+ path = base_path + ['local-route']
+
+ src = '203.0.113.11'
+ dst = '203.0.113.0/24'
+ fwmk = '23'
+ rule = '100'
+ table = '150'
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 100: from 203.0.113.11 to 203.0.113.0/24 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 100')
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', '151'])
+ self.cli_commit()
+
+ original = """
+ 100: from 203.0.113.11 to 203.0.113.0/24 fwmark 0x17 lookup 151
+ """
+ tmp = cmd('ip rule show prio 100')
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+def sort_ip(output):
+ o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()])
+ o = o.splitlines()
+ o.sort()
+ return o
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index 4875fb5d1..da08421d3 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -27,6 +27,7 @@ RADVD_CONF = '/run/radvd/radvd.conf'
interface = 'eth1'
base_path = ['service', 'router-advert', 'interface', interface]
address_base = ['interfaces', 'ethernet', interface, 'address']
+prefix = '::/64'
def get_config_value(key):
tmp = read_file(RADVD_CONF)
@@ -113,5 +114,38 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):
tmp = 'DNSSL ' + ' '.join(dnssl) + ' {'
self.assertIn(tmp, config)
+ def test_route(self):
+ route = '2001:db8:1000::/64'
+
+ self.cli_set(base_path + ['prefix', prefix])
+ self.cli_set(base_path + ['route', route])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(RADVD_CONF)
+
+ tmp = f'route {route}' + ' {'
+ self.assertIn(tmp, config)
+
+ self.assertIn('AdvRouteLifetime 1800;', config)
+ self.assertIn('AdvRoutePreference medium;', config)
+ self.assertIn('RemoveRoute on;', config)
+
+ def test_rasrcaddress(self):
+ ra_src = ['fe80::1', 'fe80::2']
+
+ self.cli_set(base_path + ['prefix', prefix])
+ for src in ra_src:
+ self.cli_set(base_path + ['source-address', src])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(RADVD_CONF)
+ self.assertIn('AdvRASrcAddress {', config)
+ for src in ra_src:
+ self.assertIn(f' {src};', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py
index dfccced0a..75486337a 100755
--- a/smoketest/scripts/cli/test_service_webproxy.py
+++ b/smoketest/scripts/cli/test_service_webproxy.py
@@ -241,8 +241,8 @@ class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase):
config = read_file(PROXY_CONF)
self.assertIn(f'http_port {listen_ip}:3128 intercept', config)
- self.assertIn(f'redirect_program /usr/bin/squidGuard -c /etc/squidguard/squidGuard.conf', config)
- self.assertIn(f'redirect_children 8', config)
+ self.assertIn(f'url_rewrite_program /usr/bin/squidGuard -c /etc/squidguard/squidGuard.conf', config)
+ self.assertIn(f'url_rewrite_children 8', config)
# Check SquidGuard config
sg_config = read_file('/etc/squidguard/squidGuard.conf')
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
new file mode 100755
index 000000000..7567444db
--- /dev/null
+++ b/src/conf_mode/container.py
@@ -0,0 +1,395 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from ipaddress import ip_address
+from ipaddress import ip_network
+from time import sleep
+from json import dumps as json_write
+
+from vyos.base import Warning
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configdict import node_changed
+from vyos.util import call
+from vyos.util import cmd
+from vyos.util import run
+from vyos.util import write_file
+from vyos.template import inc_ip
+from vyos.template import is_ipv4
+from vyos.template import is_ipv6
+from vyos.template import render
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+config_containers_registry = '/etc/containers/registries.conf'
+config_containers_storage = '/etc/containers/storage.conf'
+systemd_unit_path = '/run/systemd/system'
+
+def _cmd(command):
+ if os.path.exists('/tmp/vyos.container.debug'):
+ print(command)
+ return cmd(command)
+
+def network_exists(name):
+ # Check explicit name for network, returns True if network exists
+ c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
+ return bool(c)
+
+# Common functions
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['container']
+ container = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ # container base default values can not be merged here - remove and add them later
+ if 'name' in default_values:
+ del default_values['name']
+ container = dict_merge(default_values, container)
+
+ # Merge per-container default values
+ if 'name' in container:
+ default_values = defaults(base + ['name'])
+ if 'port' in default_values:
+ del default_values['port']
+ for name in container['name']:
+ container['name'][name] = dict_merge(default_values, container['name'][name])
+
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if 'port' in container['name'][name]:
+ for port in container['name'][name]['port']:
+ default_values = defaults(base + ['name', 'port'])
+ container['name'][name]['port'][port] = dict_merge(
+ default_values, container['name'][name]['port'][port])
+
+ # Delete container network, delete containers
+ tmp = node_changed(conf, base + ['network'])
+ if tmp: container.update({'network_remove' : tmp})
+
+ tmp = node_changed(conf, base + ['name'])
+ if tmp: container.update({'container_remove' : tmp})
+
+ return container
+
+def verify(container):
+ # bail out early - looks like removal from running config
+ if not container:
+ return None
+
+ # Add new container
+ if 'name' in container:
+ for name, container_config in container['name'].items():
+ # Container image is a mandatory option
+ if 'image' not in container_config:
+ raise ConfigError(f'Container image for "{name}" is mandatory!')
+
+ # Check if requested container image exists locally. If it does not
+ # exist locally - inform the user. This is required as there is a
+ # shared container image storage accross all VyOS images. A user can
+ # delete a container image from the system, boot into another version
+ # of VyOS and then it would fail to boot. This is to prevent any
+ # configuration error when container images are deleted from the
+ # global storage. A per image local storage would be a super waste
+ # of diskspace as there will be a full copy (up tu several GB/image)
+ # on upgrade. This is the "cheapest" and fastest solution in terms
+ # of image upgrade and deletion.
+ image = container_config['image']
+ if run(f'podman image exists {image}') != 0:
+ Warning(f'Image "{image}" used in container "{name}" does not exist '\
+ f'locally. Please use "add container image {image}" to add it '\
+ f'to the system! Container "{name}" will not be started!')
+
+ if 'network' in container_config:
+ if len(container_config['network']) > 1:
+ raise ConfigError(f'Only one network can be specified for container "{name}"!')
+
+ # Check if the specified container network exists
+ network_name = list(container_config['network'])[0]
+ if network_name not in container.get('network', {}):
+ raise ConfigError(f'Container network "{network_name}" does not exist!')
+
+ if 'address' in container_config['network'][network_name]:
+ address = container_config['network'][network_name]['address']
+ network = None
+ if is_ipv4(address):
+ network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0]
+ elif is_ipv6(address):
+ network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0]
+
+ # Specified container IP address must belong to network prefix
+ if ip_address(address) not in ip_network(network):
+ raise ConfigError(f'Used container address "{address}" not in network "{network}"!')
+
+ # We can not use the first IP address of a network prefix as this is used by podman
+ if ip_address(address) == ip_network(network)[1]:
+ raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ 'reserved for the container engine!')
+
+ if 'device' in container_config:
+ for dev, dev_config in container_config['device'].items():
+ if 'source' not in dev_config:
+ raise ConfigError(f'Device "{dev}" has no source path configured!')
+
+ if 'destination' not in dev_config:
+ raise ConfigError(f'Device "{dev}" has no destination path configured!')
+
+ source = dev_config['source']
+ if not os.path.exists(source):
+ raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')
+
+ if 'environment' in container_config:
+ for var, cfg in container_config['environment'].items():
+ if 'value' not in cfg:
+ raise ConfigError(f'Environment variable {var} has no value assigned!')
+
+ if 'volume' in container_config:
+ for volume, volume_config in container_config['volume'].items():
+ if 'source' not in volume_config:
+ raise ConfigError(f'Volume "{volume}" has no source path configured!')
+
+ if 'destination' not in volume_config:
+ raise ConfigError(f'Volume "{volume}" has no destination path configured!')
+
+ source = volume_config['source']
+ if not os.path.exists(source):
+ raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!')
+
+ if 'port' in container_config:
+ for tmp in container_config['port']:
+ if not {'source', 'destination'} <= set(container_config['port'][tmp]):
+ raise ConfigError(f'Both "source" and "destination" must be specified for a port mapping!')
+
+ # If 'allow-host-networks' or 'network' not set.
+ if 'allow_host_networks' not in container_config and 'network' not in container_config:
+ raise ConfigError(f'Must either set "network" or "allow-host-networks" for container "{name}"!')
+
+ # Can not set both allow-host-networks and network at the same time
+ if {'allow_host_networks', 'network'} <= set(container_config):
+ raise ConfigError(f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
+
+ # Add new network
+ if 'network' in container:
+ for network, network_config in container['network'].items():
+ v4_prefix = 0
+ v6_prefix = 0
+ # If ipv4-prefix not defined for user-defined network
+ if 'prefix' not in network_config:
+ raise ConfigError(f'prefix for network "{network}" must be defined!')
+
+ for prefix in network_config['prefix']:
+ if is_ipv4(prefix): v4_prefix += 1
+ elif is_ipv6(prefix): v6_prefix += 1
+
+ if v4_prefix > 1:
+ raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
+ if v6_prefix > 1:
+ raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!')
+
+
+ # A network attached to a container can not be deleted
+ if {'network_remove', 'name'} <= set(container):
+ for network in container['network_remove']:
+ for container, container_config in container['name'].items():
+ if 'network' in container_config and network in container_config['network']:
+ raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!')
+
+ return None
+
+def generate_run_arguments(name, container_config):
+ image = container_config['image']
+ memory = container_config['memory']
+ shared_memory = container_config['shared_memory']
+ restart = container_config['restart']
+
+ # Add capability options. Should be in uppercase
+ cap_add = ''
+ if 'cap_add' in container_config:
+ for c in container_config['cap_add']:
+ c = c.upper()
+ c = c.replace('-', '_')
+ cap_add += f' --cap-add={c}'
+
+ # Add a host device to the container /dev/x:/dev/x
+ device = ''
+ if 'device' in container_config:
+ for dev, dev_config in container_config['device'].items():
+ source_dev = dev_config['source']
+ dest_dev = dev_config['destination']
+ device += f' --device={source_dev}:{dest_dev}'
+
+ # Check/set environment options "-e foo=bar"
+ env_opt = ''
+ if 'environment' in container_config:
+ for k, v in container_config['environment'].items():
+ env_opt += f" -e \"{k}={v['value']}\""
+
+ # Publish ports
+ port = ''
+ if 'port' in container_config:
+ protocol = ''
+ for portmap in container_config['port']:
+ protocol = container_config['port'][portmap]['protocol']
+ sport = container_config['port'][portmap]['source']
+ dport = container_config['port'][portmap]['destination']
+ port += f' -p {sport}:{dport}/{protocol}'
+
+ # Bind volume
+ volume = ''
+ if 'volume' in container_config:
+ for vol, vol_config in container_config['volume'].items():
+ svol = vol_config['source']
+ dvol = vol_config['destination']
+ volume += f' -v {svol}:{dvol}'
+
+ container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \
+ f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
+ f'--name {name} {device} {port} {volume} {env_opt}'
+
+ if 'allow_host_networks' in container_config:
+ return f'{container_base_cmd} --net host {image}'
+
+ ip_param = ''
+ networks = ",".join(container_config['network'])
+ for network in container_config['network']:
+ if 'address' in container_config['network'][network]:
+ address = container_config['network'][network]['address']
+ ip_param = f'--ip {address}'
+
+ return f'{container_base_cmd} --net {networks} {ip_param} {image}'
+
+def generate(container):
+ # bail out early - looks like removal from running config
+ if not container:
+ if os.path.exists(config_containers_registry):
+ os.unlink(config_containers_registry)
+ if os.path.exists(config_containers_storage):
+ os.unlink(config_containers_storage)
+ return None
+
+ if 'network' in container:
+ for network, network_config in container['network'].items():
+ tmp = {
+ 'cniVersion' : '0.4.0',
+ 'name' : network,
+ 'plugins' : [{
+ 'type': 'bridge',
+ 'bridge': f'cni-{network}',
+ 'isGateway': True,
+ 'ipMasq': False,
+ 'hairpinMode': False,
+ 'ipam' : {
+ 'type': 'host-local',
+ 'routes': [],
+ 'ranges' : [],
+ },
+ }]
+ }
+
+ for prefix in network_config['prefix']:
+ net = [{'gateway' : inc_ip(prefix, 1), 'subnet' : prefix}]
+ tmp['plugins'][0]['ipam']['ranges'].append(net)
+
+ # install per address-family default orutes
+ default_route = '0.0.0.0/0'
+ if is_ipv6(prefix):
+ default_route = '::/0'
+ tmp['plugins'][0]['ipam']['routes'].append({'dst': default_route})
+
+ write_file(f'/etc/cni/net.d/{network}.conflist', json_write(tmp, indent=2))
+
+ render(config_containers_registry, 'container/registries.conf.j2', container)
+ render(config_containers_storage, 'container/storage.conf.j2', container)
+
+ if 'name' in container:
+ for name, container_config in container['name'].items():
+ if 'disable' in container_config:
+ continue
+
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ run_args = generate_run_arguments(name, container_config)
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args})
+
+ return None
+
+def apply(container):
+ # Delete old containers if needed. We can't delete running container
+ # Option "--force" allows to delete containers with any status
+ if 'container_remove' in container:
+ for name in container['container_remove']:
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ call(f'systemctl stop vyos-container-{name}.service')
+ if os.path.exists(file_path):
+ os.unlink(file_path)
+
+ call('systemctl daemon-reload')
+
+ # Delete old networks if needed
+ if 'network_remove' in container:
+ for network in container['network_remove']:
+ call(f'podman network rm {network}')
+ tmp = f'/etc/cni/net.d/{network}.conflist'
+ if os.path.exists(tmp):
+ os.unlink(tmp)
+
+ # Add container
+ disabled_new = False
+ if 'name' in container:
+ for name, container_config in container['name'].items():
+ image = container_config['image']
+
+ if run(f'podman image exists {image}') != 0:
+ # container image does not exist locally - user already got
+ # informed by a WARNING in verfiy() - bail out early
+ continue
+
+ if 'disable' in container_config:
+ # check if there is a container by that name running
+ tmp = _cmd('podman ps -a --format "{{.Names}}"')
+ if name in tmp:
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ call(f'systemctl stop vyos-container-{name}.service')
+ if os.path.exists(file_path):
+ disabled_new = True
+ os.unlink(file_path)
+ continue
+
+ cmd(f'systemctl restart vyos-container-{name}.service')
+
+ if disabled_new:
+ call('systemctl daemon-reload')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py
index 67bf5d0e2..b7f4aa82c 100755
--- a/src/conf_mode/firewall_options.py
+++ b/src/conf_mode/firewall_options.py
@@ -115,9 +115,12 @@ def apply(tcp):
continue
# adjust TCP MSS per interface
- if mss:
+ if mss == 'clamp-mss-to-pmtu':
call('iptables --table mangle --append {} --out-interface {} --protocol tcp '
- '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
+ '--tcp-flags SYN,RST SYN --jump TCPMSS --clamp-mss-to-pmtu >&/dev/null'.format(target, intf))
+ elif mss:
+ call('iptables --table mangle --append {} --out-interface {} --protocol tcp '
+ '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
# Setup new ip6tables rules
if tcp['new_chain6']:
@@ -133,9 +136,12 @@ def apply(tcp):
continue
# adjust TCP MSS per interface
- if mss:
+ if mss == 'clamp-mss-to-pmtu':
+ call('ip6tables --table mangle --append {} --out-interface {} --protocol tcp '
+ '--tcp-flags SYN,RST SYN --jump TCPMSS --clamp-mss-to-pmtu >&/dev/null'.format(target, intf))
+ elif mss:
call('ip6tables --table mangle --append {} --out-interface {} --protocol tcp '
- '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
+ '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
return None
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
index 013f22665..8a92bbc76 100755
--- a/src/conf_mode/policy-local-route.py
+++ b/src/conf_mode/policy-local-route.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,6 +18,7 @@ import os
from sys import exit
+from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
@@ -35,26 +36,103 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['policy', 'local-route']
+ base = ['policy']
+
pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # delete policy local-route
- dict = {}
- tmp = node_changed(conf, ['policy', 'local-route', 'rule'], key_mangling=('-', '_'))
- if tmp:
- for rule in (tmp or []):
- src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
- if src:
- dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
+ for route in ['local_route', 'local_route6']:
+ dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove'
+ route_key = 'local-route' if route == 'local_route' else 'local-route6'
+ base_rule = base + [route_key, 'rule']
+
+ # delete policy local-route
+ dict = {}
+ tmp = node_changed(conf, base_rule, key_mangling=('-', '_'))
+ if tmp:
+ for rule in (tmp or []):
+ src = leaf_node_changed(conf, base_rule + [rule, 'source'])
+ fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
+ iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
+ dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table'])
+ rule_def = {}
+ if src:
+ rule_def = dict_merge({'source' : src}, rule_def)
+ if fwmk:
+ rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
+ if iif:
+ rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
+ if dst:
+ rule_def = dict_merge({'destination' : dst}, rule_def)
+ if table:
+ rule_def = dict_merge({'table' : table}, rule_def)
+ dict = dict_merge({dict_id : {rule : rule_def}}, dict)
pbr.update(dict)
- # delete policy local-route rule x source x.x.x.x
- if 'rule' in pbr:
- for rule in pbr['rule']:
- src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
- if src:
- dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
- pbr.update(dict)
+ if not route in pbr:
+ continue
+
+ # delete policy local-route rule x source x.x.x.x
+ # delete policy local-route rule x fwmark x
+ # delete policy local-route rule x destination x.x.x.x
+ if 'rule' in pbr[route]:
+ for rule, rule_config in pbr[route]['rule'].items():
+ src = leaf_node_changed(conf, base_rule + [rule, 'source'])
+ fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
+ iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
+ dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table'])
+ # keep track of changes in configuration
+ # otherwise we might remove an existing node although nothing else has changed
+ changed = False
+
+ rule_def = {}
+ # src is None if there are no changes to src
+ if src is None:
+ # if src hasn't changed, include it in the removal selector
+ # if a new selector is added, we have to remove all previous rules without this selector
+ # to make sure we remove all previous rules with this source(s), it will be included
+ if 'source' in rule_config:
+ rule_def = dict_merge({'source': rule_config['source']}, rule_def)
+ else:
+ # if src is not None, it's previous content will be returned
+ # this can be an empty array if it's just being set, or the previous value
+ # either way, something has to be changed and we only want to remove previous values
+ changed = True
+ # set the old value for removal if it's not empty
+ if len(src) > 0:
+ rule_def = dict_merge({'source' : src}, rule_def)
+ if fwmk is None:
+ if 'fwmark' in rule_config:
+ rule_def = dict_merge({'fwmark': [rule_config['fwmark']]}, rule_def)
+ else:
+ changed = True
+ if len(fwmk) > 0:
+ rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
+ if iif is None:
+ if 'inbound_interface' in rule_config:
+ rule_def = dict_merge({'inbound_interface': [rule_config['inbound_interface']]}, rule_def)
+ else:
+ changed = True
+ if len(iif) > 0:
+ rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
+ if dst is None:
+ if 'destination' in rule_config:
+ rule_def = dict_merge({'destination': rule_config['destination']}, rule_def)
+ else:
+ changed = True
+ if len(dst) > 0:
+ rule_def = dict_merge({'destination' : dst}, rule_def)
+ if table is None:
+ if 'set' in rule_config and 'table' in rule_config['set']:
+ rule_def = dict_merge({'table': [rule_config['set']['table']]}, rule_def)
+ else:
+ changed = True
+ if len(table) > 0:
+ rule_def = dict_merge({'table' : table}, rule_def)
+ if changed:
+ dict = dict_merge({dict_id : {rule : rule_def}}, dict)
+ pbr.update(dict)
return pbr
@@ -63,13 +141,25 @@ def verify(pbr):
if not pbr:
return None
- if 'rule' in pbr:
- for rule in pbr['rule']:
- if 'source' not in pbr['rule'][rule]:
- raise ConfigError('Source address required!')
- else:
- if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']:
- raise ConfigError('Table set is required!')
+ for route in ['local_route', 'local_route6']:
+ if not route in pbr:
+ continue
+
+ pbr_route = pbr[route]
+ if 'rule' in pbr_route:
+ for rule in pbr_route['rule']:
+ if 'source' not in pbr_route['rule'][rule] \
+ and 'destination' not in pbr_route['rule'][rule] \
+ and 'fwmark' not in pbr_route['rule'][rule] \
+ and 'inbound_interface' not in pbr_route['rule'][rule]:
+ raise ConfigError('Source or destination address or fwmark or inbound-interface is required!')
+ else:
+ if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']:
+ raise ConfigError('Table set is required!')
+ if 'inbound_interface' in pbr_route['rule'][rule]:
+ interface = pbr_route['rule'][rule]['inbound_interface']
+ if interface not in interfaces():
+ raise ConfigError(f'Interface "{interface}" does not exist')
return None
@@ -84,18 +174,54 @@ def apply(pbr):
return None
# Delete old rule if needed
- if 'rule_remove' in pbr:
- for rule in pbr['rule_remove']:
- for src in pbr['rule_remove'][rule]['source']:
- call(f'ip rule del prio {rule} from {src}')
+ for rule_rm in ['rule_remove', 'rule6_remove']:
+ if rule_rm in pbr:
+ v6 = " -6" if rule_rm == 'rule6_remove' else ""
+ for rule, rule_config in pbr[rule_rm].items():
+ rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['']
+ for src in rule_config['source']:
+ f_src = '' if src == '' else f' from {src} '
+ rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['']
+ for dst in rule_config['destination']:
+ f_dst = '' if dst == '' else f' to {dst} '
+ rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else ['']
+ for fwmk in rule_config['fwmark']:
+ f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
+ rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else ['']
+ for iif in rule_config['inbound_interface']:
+ f_iif = '' if iif == '' else f' iif {iif} '
+ rule_config['table'] = rule_config['table'] if 'table' in rule_config else ['']
+ for table in rule_config['table']:
+ f_table = '' if table == '' else f' lookup {table} '
+ call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}{f_table}')
# Generate new config
- if 'rule' in pbr:
- for rule in pbr['rule']:
- table = pbr['rule'][rule]['set']['table']
- if pbr['rule'][rule]['source']:
- for src in pbr['rule'][rule]['source']:
- call(f'ip rule add prio {rule} from {src} lookup {table}')
+ for route in ['local_route', 'local_route6']:
+ if not route in pbr:
+ continue
+
+ v6 = " -6" if route == 'local_route6' else ""
+
+ pbr_route = pbr[route]
+ if 'rule' in pbr_route:
+ for rule, rule_config in pbr_route['rule'].items():
+ table = rule_config['set']['table']
+
+ rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all']
+ for src in rule_config['source'] or ['all']:
+ f_src = '' if src == '' else f' from {src} '
+ rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all']
+ for dst in rule_config['destination']:
+ f_dst = '' if dst == '' else f' to {dst} '
+ f_fwmk = ''
+ if 'fwmark' in rule_config:
+ fwmk = rule_config['fwmark']
+ f_fwmk = f' fwmark {fwmk} '
+ f_iif = ''
+ if 'inbound_interface' in rule_config:
+ iif = rule_config['inbound_interface']
+ f_iif = f' iif {iif} '
+ call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}')
return None
diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py
index cbbd2e0bc..59c087aaa 100755
--- a/src/conf_mode/service_webproxy.py
+++ b/src/conf_mode/service_webproxy.py
@@ -23,12 +23,15 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
from vyos.util import call
+from vyos.util import chmod_755
from vyos.util import dict_search
from vyos.util import write_file
from vyos.validate import is_addr_assigned
from vyos.xml import defaults
+from vyos.base import Warning
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
squid_config_file = '/etc/squid/squid.conf'
@@ -36,24 +39,56 @@ squidguard_config_file = '/etc/squidguard/squidGuard.conf'
squidguard_db_dir = '/opt/vyatta/etc/config/url-filtering/squidguard/db'
user_group = 'proxy'
-def generate_sg_localdb(category, list_type, role, proxy):
+
+def check_blacklist_categorydb(config_section):
+ if 'block_category' in config_section:
+ for category in config_section['block_category']:
+ check_categorydb(category)
+ if 'allow_category' in config_section:
+ for category in config_section['allow_category']:
+ check_categorydb(category)
+
+
+def check_categorydb(category: str):
+ """
+ Check if category's db exist
+ :param category:
+ :type str:
+ """
+ path_to_cat: str = f'{squidguard_db_dir}/{category}'
+ if not os.path.exists(f'{path_to_cat}/domains.db') \
+ and not os.path.exists(f'{path_to_cat}/urls.db') \
+ and not os.path.exists(f'{path_to_cat}/expressions.db'):
+ Warning(f'DB of category {category} does not exist.\n '
+ f'Use [update webproxy blacklists] '
+ f'or delete undefined category!')
+
+
+def generate_sg_rule_localdb(category, list_type, role, proxy):
+ if not category or not list_type or not role:
+ return None
cat_ = category.replace('-', '_')
- if isinstance(dict_search(f'url_filtering.squidguard.{cat_}', proxy),
- list):
+ if role == 'default':
+ path_to_cat = f'{cat_}'
+ else:
+ path_to_cat = f'rule.{role}.{cat_}'
+ if isinstance(
+ dict_search(f'url_filtering.squidguard.{path_to_cat}', proxy),
+ list):
# local block databases must be generated "on-the-fly"
tmp = {
- 'squidguard_db_dir' : squidguard_db_dir,
- 'category' : f'{category}-default',
- 'list_type' : list_type,
- 'rule' : role
+ 'squidguard_db_dir': squidguard_db_dir,
+ 'category': f'{category}-{role}',
+ 'list_type': list_type,
+ 'rule': role
}
sg_tmp_file = '/tmp/sg.conf'
- db_file = f'{category}-default/{list_type}'
- domains = '\n'.join(dict_search(f'url_filtering.squidguard.{cat_}', proxy))
-
+ db_file = f'{category}-{role}/{list_type}'
+ domains = '\n'.join(
+ dict_search(f'url_filtering.squidguard.{path_to_cat}', proxy))
# local file
- write_file(f'{squidguard_db_dir}/{category}-default/local', '',
+ write_file(f'{squidguard_db_dir}/{category}-{role}/local', '',
user=user_group, group=user_group)
# database input file
write_file(f'{squidguard_db_dir}/{db_file}', domains,
@@ -63,17 +98,18 @@ def generate_sg_localdb(category, list_type, role, proxy):
render(sg_tmp_file, 'squid/sg_acl.conf.tmpl', tmp,
user=user_group, group=user_group)
- call(f'su - {user_group} -c "squidGuard -d -c {sg_tmp_file} -C {db_file}"')
+ call(
+ f'su - {user_group} -c "squidGuard -d -c {sg_tmp_file} -C {db_file}"')
if os.path.exists(sg_tmp_file):
os.unlink(sg_tmp_file)
-
else:
# if category is not part of our configuration, clean out the
# squidguard lists
- tmp = f'{squidguard_db_dir}/{category}-default'
+ tmp = f'{squidguard_db_dir}/{category}-{role}'
if os.path.exists(tmp):
- rmtree(f'{squidguard_db_dir}/{category}-default')
+ rmtree(f'{squidguard_db_dir}/{category}-{role}')
+
def get_config(config=None):
if config:
@@ -84,7 +120,8 @@ def get_config(config=None):
if not conf.exists(base):
return None
- proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ proxy = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
@@ -109,10 +146,11 @@ def get_config(config=None):
default_values = defaults(base + ['cache-peer'])
for peer in proxy['cache_peer']:
proxy['cache_peer'][peer] = dict_merge(default_values,
- proxy['cache_peer'][peer])
+ proxy['cache_peer'][peer])
return proxy
+
def verify(proxy):
if not proxy:
return None
@@ -169,17 +207,30 @@ def generate(proxy):
render(squidguard_config_file, 'squid/squidGuard.conf.tmpl', proxy)
cat_dict = {
- 'local-block' : 'domains',
- 'local-block-keyword' : 'expressions',
- 'local-block-url' : 'urls',
- 'local-ok' : 'domains',
- 'local-ok-url' : 'urls'
+ 'local-block': 'domains',
+ 'local-block-keyword': 'expressions',
+ 'local-block-url': 'urls',
+ 'local-ok': 'domains',
+ 'local-ok-url': 'urls'
}
- for category, list_type in cat_dict.items():
- generate_sg_localdb(category, list_type, 'default', proxy)
+ if dict_search(f'url_filtering.squidguard', proxy) is not None:
+ squidgard_config_section = proxy['url_filtering']['squidguard']
+
+ for category, list_type in cat_dict.items():
+ generate_sg_rule_localdb(category, list_type, 'default', proxy)
+ check_blacklist_categorydb(squidgard_config_section)
+
+ if 'rule' in squidgard_config_section:
+ for rule in squidgard_config_section['rule']:
+ rule_config_section = squidgard_config_section['rule'][
+ rule]
+ for category, list_type in cat_dict.items():
+ generate_sg_rule_localdb(category, list_type, rule, proxy)
+ check_blacklist_categorydb(rule_config_section)
return None
+
def apply(proxy):
if not proxy:
# proxy is removed in the commit
@@ -192,9 +243,12 @@ def apply(proxy):
return None
- call('systemctl restart squid.service')
+ if os.path.exists(squidguard_db_dir):
+ chmod_755(squidguard_db_dir)
+ call('systemctl reload-or-restart squid.service')
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py
index ddb91aeaf..a112c2b6f 100755
--- a/src/conf_mode/system-option.py
+++ b/src/conf_mode/system-option.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -22,16 +22,18 @@ from time import sleep
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configverify import verify_source_interface
from vyos.template import render
from vyos.util import cmd
from vyos.validate import is_addr_assigned
+from vyos.validate import is_intf_addr_assigned
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
curlrc_config = r'/etc/curlrc'
-ssh_config = r'/etc/ssh/ssh_config'
+ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target'
def get_config(config=None):
@@ -67,8 +69,17 @@ def verify(options):
if 'ssh_client' in options:
config = options['ssh_client']
if 'source_address' in config:
+ address = config['source_address']
if not is_addr_assigned(config['source_address']):
- raise ConfigError('No interface with give address specified!')
+ raise ConfigError('No interface with address "{address}" configured!')
+
+ if 'source_interface' in config:
+ verify_source_interface(config)
+ if 'source_address' in config:
+ address = config['source_address']
+ interface = config['source_interface']
+ if not is_intf_addr_assigned(interface, address):
+ raise ConfigError(f'Address "{address}" not assigned on interface "{interface}"!')
return None
diff --git a/src/migration-scripts/container/0-to-1 b/src/migration-scripts/container/0-to-1
new file mode 100755
index 000000000..d0461389b
--- /dev/null
+++ b/src/migration-scripts/container/0-to-1
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T4870: change underlaying container filesystem from vfs to overlay
+
+import os
+import shutil
+import sys
+
+from vyos.configtree import ConfigTree
+from vyos.util import call
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['container', 'name']
+config = ConfigTree(config_file)
+
+# Check if containers exist and we need to perform image manipulation
+if config.exists(base):
+ for container in config.list_nodes(base):
+ # Stop any given container first
+ call(f'systemctl stop vyos-container-{container}.service')
+ # Export container image for later re-import to new filesystem. We store
+ # the backup on a real disk as a tmpfs (like /tmp) could probably lack
+ # memory if a host has too many containers stored.
+ image_name = config.return_value(base + [container, 'image'])
+ call(f'podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}')
+
+# No need to adjust the strage driver online (this is only used for testing and
+# debugging on a live system) - it is already overlay2 when the migration script
+# is run during system update. But the specified driver in the image is actually
+# overwritten by the still present VFS filesystem on disk. Thus podman still
+# thinks it uses VFS until we delete the libpod directory under:
+# /usr/lib/live/mount/persistence/container/storage
+#call('sed -i "s/vfs/overlay2/g" /etc/containers/storage.conf /usr/share/vyos/templates/container/storage.conf.j2')
+
+base_path = '/usr/lib/live/mount/persistence/container/storage'
+for dir in ['libpod', 'vfs', 'vfs-containers', 'vfs-images', 'vfs-layers']:
+ if os.path.exists(f'{base_path}/{dir}'):
+ shutil.rmtree(f'{base_path}/{dir}')
+
+# Now all remaining information about VFS is gone and we operate in overlayfs2
+# filesystem mode. Time to re-import the images.
+if config.exists(base):
+ for container in config.list_nodes(base):
+ # Export container image for later re-import to new filesystem
+ image_name = config.return_value(base + [container, 'image'])
+ image_path = f'/root/{container}.tar'
+ call(f'podman image load --quiet --input {image_path}')
+
+ # Start any given container first
+ call(f'systemctl start vyos-container-{container}.service')
+
+ # Delete temporary container image
+ if os.path.exists(image_path):
+ os.unlink(image_path)
+
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
new file mode 100755
index 000000000..ecefc556e
--- /dev/null
+++ b/src/op_mode/container.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import sys
+
+from sys import exit
+
+from vyos.util import cmd
+
+import vyos.opmode
+
+def _get_json_data(command: str) -> list:
+ """
+ Get container command format JSON
+ """
+ return cmd(f'{command} --format json')
+
+
+def _get_raw_data(command: str) -> list:
+ json_data = _get_json_data(command)
+ data = json.loads(json_data)
+ return data
+
+
+def show_container(raw: bool):
+ command = 'podman ps --all'
+ container_data = _get_raw_data(command)
+ if raw:
+ return container_data
+ else:
+ return cmd(command)
+
+
+def show_image(raw: bool):
+ command = 'podman image ls'
+ container_data = _get_raw_data('podman image ls')
+ if raw:
+ return container_data
+ else:
+ return cmd(command)
+
+
+def show_network(raw: bool):
+ command = 'podman network ls'
+ container_data = _get_raw_data(command)
+ if raw:
+ return container_data
+ else:
+ return cmd(command)
+
+
+def restart(name: str):
+ from vyos.util import rc_cmd
+
+ rc, output = rc_cmd(f'systemctl restart vyos-container-{name}.service')
+ if rc != 0:
+ print(output)
+ return None
+ print(f'Container name "{name}" restarted!')
+ return output
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/show_ipsec_connections.py b/src/op_mode/show_ipsec_connections.py
new file mode 100755
index 000000000..4ca8f8e51
--- /dev/null
+++ b/src/op_mode/show_ipsec_connections.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+from vyos.util import convert_data
+
+
+def _get_vici_sas():
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except PermissionError:
+ print("You do not have a permission to connect to the IPsec daemon")
+ sys.exit(1)
+ except ConnectionRefusedError:
+ print("IPsec is not runing")
+ sys.exit(1)
+ except Exception as e:
+ print("An error occured: {0}".format(e))
+ sys.exit(1)
+ sas = list(session.list_sas())
+ return convert_data(sas)
+
+
+def _get_vici_connections():
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except PermissionError:
+ print("You do not have a permission to connect to the IPsec daemon")
+ sys.exit(1)
+ except ConnectionRefusedError:
+ print("IPsec is not runing")
+ sys.exit(1)
+ except Exception as e:
+ print("An error occured: {0}".format(e))
+ sys.exit(1)
+ connections = list(session.list_conns())
+ return convert_data(connections)
+
+
+def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
+ """Get parent SA proposals by connection name
+ if connections not in the 'down' state
+ Args:
+ connection_name (str): Connection name
+ data (list): List of current SAs from vici
+ Returns:
+ str: Parent SA connection proposal
+ AES_CBC/256/HMAC_SHA2_256_128/MODP_1024
+ """
+ if not data:
+ return {}
+ for sa in data:
+ # check if parent SA exist
+ if connection_name not in sa.keys():
+ return {}
+ if 'encr-alg' in sa[connection_name]:
+ encr_alg = sa.get(connection_name, '').get('encr-alg')
+ cipher = encr_alg.split('_')[0]
+ mode = encr_alg.split('_')[1]
+ encr_keysize = sa.get(connection_name, '').get('encr-keysize')
+ integ_alg = sa.get(connection_name, '').get('integ-alg')
+ # prf_alg = sa.get(connection_name, '').get('prf-alg')
+ dh_group = sa.get(connection_name, '').get('dh-group')
+ proposal = {
+ 'cipher': cipher,
+ 'mode': mode,
+ 'key_size': encr_keysize,
+ 'hash': integ_alg,
+ 'dh': dh_group
+ }
+ return proposal
+ return {}
+
+
+def _get_parent_sa_state(connection_name: str, data: list) -> str:
+ """Get parent SA state by connection name
+ Args:
+ connection_name (str): Connection name
+ data (list): List of current SAs from vici
+ Returns:
+ Parent SA connection state
+ """
+ if not data:
+ return 'down'
+ for sa in data:
+ # check if parent SA exist
+ if connection_name not in sa.keys():
+ return 'down'
+ if sa[connection_name]['state'].lower() == 'established':
+ return 'up'
+ else:
+ return 'down'
+
+
+def _get_child_sa_state(connection_name: str, tunnel_name: str,
+ data: list) -> str:
+ """Get child SA state by connection and tunnel name
+ Args:
+ connection_name (str): Connection name
+ tunnel_name (str): Tunnel name
+ data (list): List of current SAs from vici
+ Returns:
+ str: `up` if child SA state is 'installed' otherwise `down`
+ """
+ if not data:
+ return 'down'
+ for sa in data:
+ # check if parent SA exist
+ if connection_name not in sa.keys():
+ return 'down'
+ child_sas = sa[connection_name]['child-sas']
+ # Get all child SA states
+ # there can be multiple SAs per tunnel
+ child_sa_states = [
+ v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name
+ ]
+ return 'up' if 'INSTALLED' in child_sa_states else 'down'
+
+
+def _get_child_sa_info(connection_name: str, tunnel_name: str,
+ data: list) -> dict:
+ """Get child SA installed info by connection and tunnel name
+ Args:
+ connection_name (str): Connection name
+ tunnel_name (str): Tunnel name
+ data (list): List of current SAs from vici
+ Returns:
+ dict: Info of the child SA in the dictionary format
+ """
+ for sa in data:
+ # check if parent SA exist
+ if connection_name not in sa.keys():
+ return {}
+ child_sas = sa[connection_name]['child-sas']
+ # Get all child SA data
+ # Skip temp SA name (first key), get only SA values as dict
+ # {'OFFICE-B-tunnel-0-46': {'name': 'OFFICE-B-tunnel-0'}...}
+ # i.e get all data after 'OFFICE-B-tunnel-0-46'
+ child_sa_info = [
+ v for k, v in child_sas.items() if 'name' in v and
+ v['name'] == tunnel_name and v['state'] == 'INSTALLED'
+ ]
+ return child_sa_info[-1] if child_sa_info else {}
+
+
+def _get_child_sa_proposal(child_sa_data: dict) -> dict:
+ if child_sa_data and 'encr-alg' in child_sa_data:
+ encr_alg = child_sa_data.get('encr-alg')
+ cipher = encr_alg.split('_')[0]
+ mode = encr_alg.split('_')[1]
+ key_size = child_sa_data.get('encr-keysize')
+ integ_alg = child_sa_data.get('integ-alg')
+ dh_group = child_sa_data.get('dh-group')
+ proposal = {
+ 'cipher': cipher,
+ 'mode': mode,
+ 'key_size': key_size,
+ 'hash': integ_alg,
+ 'dh': dh_group
+ }
+ return proposal
+ return {}
+
+
+def _get_raw_data_connections(list_connections: list, list_sas: list) -> list:
+ """Get configured VPN IKE connections and IPsec states
+ Args:
+ list_connections (list): List of configured connections from vici
+ list_sas (list): List of current SAs from vici
+ Returns:
+ list: List and status of IKE/IPsec connections/tunnels
+ """
+ base_dict = []
+ for connections in list_connections:
+ base_list = {}
+ for connection, conn_conf in connections.items():
+ base_list['ike_connection_name'] = connection
+ base_list['ike_connection_state'] = _get_parent_sa_state(
+ connection, list_sas)
+ base_list['ike_remote_address'] = conn_conf['remote_addrs']
+ base_list['ike_proposal'] = _get_parent_sa_proposal(
+ connection, list_sas)
+ base_list['local_id'] = conn_conf.get('local-1', '').get('id')
+ base_list['remote_id'] = conn_conf.get('remote-1', '').get('id')
+ base_list['version'] = conn_conf.get('version', 'IKE')
+ base_list['children'] = []
+ children = conn_conf['children']
+ for tunnel, tun_options in children.items():
+ state = _get_child_sa_state(connection, tunnel, list_sas)
+ local_ts = tun_options.get('local-ts')
+ remote_ts = tun_options.get('remote-ts')
+ dpd_action = tun_options.get('dpd_action')
+ close_action = tun_options.get('close_action')
+ sa_info = _get_child_sa_info(connection, tunnel, list_sas)
+ esp_proposal = _get_child_sa_proposal(sa_info)
+ base_list['children'].append({
+ 'name': tunnel,
+ 'state': state,
+ 'local_ts': local_ts,
+ 'remote_ts': remote_ts,
+ 'dpd_action': dpd_action,
+ 'close_action': close_action,
+ 'esp_proposal': esp_proposal
+ })
+ base_dict.append(base_list)
+ return base_dict
+
+
+def _get_formatted_output_conections(data):
+ from tabulate import tabulate
+ data_entries = ''
+ connections = []
+ for entry in data:
+ tunnels = []
+ ike_name = entry['ike_connection_name']
+ ike_state = entry['ike_connection_state']
+ conn_type = entry.get('version', 'IKE')
+ remote_addrs = ','.join(entry['ike_remote_address'])
+ local_ts, remote_ts = '-', '-'
+ local_id = entry['local_id']
+ remote_id = entry['remote_id']
+ proposal = '-'
+ if entry.get('ike_proposal'):
+ proposal = (f'{entry["ike_proposal"]["cipher"]}_'
+ f'{entry["ike_proposal"]["mode"]}/'
+ f'{entry["ike_proposal"]["key_size"]}/'
+ f'{entry["ike_proposal"]["hash"]}/'
+ f'{entry["ike_proposal"]["dh"]}')
+ connections.append([
+ ike_name, ike_state, conn_type, remote_addrs, local_ts, remote_ts,
+ local_id, remote_id, proposal
+ ])
+ for tun in entry['children']:
+ tun_name = tun.get('name')
+ tun_state = tun.get('state')
+ conn_type = 'IPsec'
+ local_ts = '\n'.join(tun.get('local_ts'))
+ remote_ts = '\n'.join(tun.get('remote_ts'))
+ proposal = '-'
+ if tun.get('esp_proposal'):
+ proposal = (f'{tun["esp_proposal"]["cipher"]}_'
+ f'{tun["esp_proposal"]["mode"]}/'
+ f'{tun["esp_proposal"]["key_size"]}/'
+ f'{tun["esp_proposal"]["hash"]}/'
+ f'{tun["esp_proposal"]["dh"]}')
+ connections.append([
+ tun_name, tun_state, conn_type, remote_addrs, local_ts,
+ remote_ts, local_id, remote_id, proposal
+ ])
+ connection_headers = [
+ 'Connection', 'State', 'Type', 'Remote address', 'Local TS',
+ 'Remote TS', 'Local id', 'Remote id', 'Proposal'
+ ]
+ output = tabulate(connections, connection_headers, numalign='left')
+ return output
+
+
+def main():
+ list_conns = _get_vici_connections()
+ list_sas = _get_vici_sas()
+ connections = _get_raw_data_connections(list_conns, list_sas)
+ return _get_formatted_output_conections(connections)
+
+
+if __name__ == '__main__':
+ print(main())
diff --git a/src/op_mode/webproxy_update_blacklist.sh b/src/op_mode/webproxy_update_blacklist.sh
index 43a4b79fc..4fb9a54c6 100755
--- a/src/op_mode/webproxy_update_blacklist.sh
+++ b/src/op_mode/webproxy_update_blacklist.sh
@@ -18,6 +18,23 @@ blacklist_url='ftp://ftp.univ-tlse1.fr/pub/reseau/cache/squidguard_contrib/black
data_dir="/opt/vyatta/etc/config/url-filtering"
archive="${data_dir}/squidguard/archive"
db_dir="${data_dir}/squidguard/db"
+conf_file="/etc/squidguard/squidGuard.conf"
+tmp_conf_file="/tmp/sg_update_db.conf"
+
+#$1-category
+#$2-type
+#$3-list
+create_sg_db ()
+{
+ FILE=$db_dir/$1/$2
+ if test -f "$FILE"; then
+ rm -f ${tmp_conf_file}
+ printf "dbhome $db_dir\ndest $1 {\n $3 $1/$2\n}\nacl {\n default {\n pass any\n }\n}" >> ${tmp_conf_file}
+ /usr/bin/squidGuard -b -c ${tmp_conf_file} -C $FILE
+ rm -f ${tmp_conf_file}
+ fi
+
+}
while [ $# -gt 0 ]
do
@@ -88,7 +105,17 @@ if [[ -n $update ]] && [[ $update -eq "yes" ]]; then
# fix permissions
chown -R proxy:proxy ${db_dir}
- chmod 2770 ${db_dir}
+
+ #create db
+ category_list=(`find $db_dir -type d -exec basename {} \; `)
+ for category in ${category_list[@]}
+ do
+ create_sg_db $category "domains" "domainlist"
+ create_sg_db $category "urls" "urllist"
+ create_sg_db $category "expressions" "expressionlist"
+ done
+ chown -R proxy:proxy ${db_dir}
+ chmod 755 ${db_dir}
logger --priority WARNING "webproxy blacklist entries updated (${count_before}/${count_after})"