import cloud-init-21.1-7.el8

This commit is contained in:
CentOS Sources 2021-10-05 21:43:02 -04:00 committed by Stepan Oksanichenko
parent 2c9d6ad674
commit e0f597afc3
27 changed files with 3497 additions and 1659 deletions

View File

@ -1 +1 @@
cbde66f717b7883c4ab64b145042de54f131afab SOURCES/cloud-init-20.3.tar.gz 2ae378aa2ae23b34b0ff123623ba5e2fbdc4928d SOURCES/cloud-init-21.1.tar.gz

2
.gitignore vendored
View File

@ -1 +1 @@
SOURCES/cloud-init-20.3.tar.gz SOURCES/cloud-init-21.1.tar.gz

View File

@ -1,8 +1,18 @@
From 25ea7a28d69518319ae1ed1b3cd510147868fd29 Mon Sep 17 00:00:00 2001 From 074cb9b011623849cfa95c1d7cc813bb28f03ff0 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com> From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Oct 2020 13:49:36 +0200 Date: Fri, 7 May 2021 13:36:03 +0200
Subject: Add initial redhat setup Subject: Add initial redhat setup
Merged patches (21.1):
- 915d30ad Change gating file to correct rhel version
- 311f318d Removing net-tools dependency
- 74731806 Adding man pages to Red Hat spec file
- 758d333d Removing blocking test from yaml configuration file
- c7e7c59c Changing permission of cloud-init-generator to 755
- 8b85abbb Installing man pages in the correct place with correct permissions
- c6808d8d Fix unit failure of cloud-final.service if NetworkManager was not present.
- 11866ef6 Report full specific version with "cloud-init --version"
Rebase notes (18.5): Rebase notes (18.5):
- added bash_completition file - added bash_completition file
- added cloud-id file - added cloud-id file
@ -33,36 +43,36 @@ setup.py:
Signed-off-by: Eduardo Otubo <otubo@redhat.com> Signed-off-by: Eduardo Otubo <otubo@redhat.com>
--- ---
.gitignore | 1 + .gitignore | 1 +
cloudinit/config/cc_chef.py | 67 ++++- cloudinit/config/cc_chef.py | 67 +++-
cloudinit/settings.py | 7 +- cloudinit/settings.py | 7 +-
redhat/.gitignore | 1 + redhat/.gitignore | 1 +
redhat/Makefile | 71 +++++ redhat/Makefile | 71 ++++
redhat/Makefile.common | 37 +++ redhat/Makefile.common | 37 ++
redhat/cloud-init-tmpfiles.conf | 1 + redhat/cloud-init-tmpfiles.conf | 1 +
redhat/cloud-init.spec.template | 517 ++++++++++++++++++++++++++++++++++ redhat/cloud-init.spec.template | 530 ++++++++++++++++++++++++++
redhat/gating.yaml | 9 + redhat/gating.yaml | 8 +
redhat/rpmbuild/BUILD/.gitignore | 3 + redhat/rpmbuild/BUILD/.gitignore | 3 +
redhat/rpmbuild/RPMS/.gitignore | 3 + redhat/rpmbuild/RPMS/.gitignore | 3 +
redhat/rpmbuild/SOURCES/.gitignore | 3 + redhat/rpmbuild/SOURCES/.gitignore | 3 +
redhat/rpmbuild/SPECS/.gitignore | 3 + redhat/rpmbuild/SPECS/.gitignore | 3 +
redhat/rpmbuild/SRPMS/.gitignore | 3 + redhat/rpmbuild/SRPMS/.gitignore | 3 +
redhat/scripts/frh.py | 27 ++ redhat/scripts/frh.py | 27 ++
redhat/scripts/git-backport-diff | 327 +++++++++++++++++++++ redhat/scripts/git-backport-diff | 327 ++++++++++++++++
redhat/scripts/git-compile-check | 215 ++++++++++++++ redhat/scripts/git-compile-check | 215 +++++++++++
redhat/scripts/process-patches.sh | 77 +++++ redhat/scripts/process-patches.sh | 77 ++++
redhat/scripts/tarball_checksum.sh | 3 + redhat/scripts/tarball_checksum.sh | 3 +
rhel/README.rhel | 5 + rhel/README.rhel | 5 +
rhel/cloud-init-tmpfiles.conf | 1 + rhel/cloud-init-tmpfiles.conf | 1 +
rhel/cloud.cfg | 69 +++++ rhel/cloud.cfg | 69 ++++
rhel/systemd/cloud-config.service | 18 ++ rhel/systemd/cloud-config.service | 18 +
rhel/systemd/cloud-config.target | 11 + rhel/systemd/cloud-config.target | 11 +
rhel/systemd/cloud-final.service | 19 ++ rhel/systemd/cloud-final.service | 24 ++
rhel/systemd/cloud-init-local.service | 31 ++ rhel/systemd/cloud-init-local.service | 31 ++
rhel/systemd/cloud-init.service | 25 ++ rhel/systemd/cloud-init.service | 25 ++
rhel/systemd/cloud-init.target | 7 + rhel/systemd/cloud-init.target | 7 +
setup.py | 23 +- setup.py | 23 +-
tools/read-version | 28 +- tools/read-version | 28 +-
30 files changed, 1562 insertions(+), 50 deletions(-) 30 files changed, 1579 insertions(+), 50 deletions(-)
create mode 100644 redhat/.gitignore create mode 100644 redhat/.gitignore
create mode 100644 redhat/Makefile create mode 100644 redhat/Makefile
create mode 100644 redhat/Makefile.common create mode 100644 redhat/Makefile.common
@ -90,7 +100,7 @@ Signed-off-by: Eduardo Otubo <otubo@redhat.com>
create mode 100644 rhel/systemd/cloud-init.target create mode 100644 rhel/systemd/cloud-init.target
diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py
index aaf7136..97ef649 100644 index aaf71366..97ef649a 100644
--- a/cloudinit/config/cc_chef.py --- a/cloudinit/config/cc_chef.py
+++ b/cloudinit/config/cc_chef.py +++ b/cloudinit/config/cc_chef.py
@@ -6,7 +6,70 @@ @@ -6,7 +6,70 @@
@ -175,10 +185,10 @@ index aaf7136..97ef649 100644
REQUIRED_CHEF_DIRS = tuple([ REQUIRED_CHEF_DIRS = tuple([
'/etc/chef', '/etc/chef',
diff --git a/cloudinit/settings.py b/cloudinit/settings.py diff --git a/cloudinit/settings.py b/cloudinit/settings.py
index ca4ffa8..3a04a58 100644 index 91e1bfe7..e690c0fd 100644
--- a/cloudinit/settings.py --- a/cloudinit/settings.py
+++ b/cloudinit/settings.py +++ b/cloudinit/settings.py
@@ -46,13 +46,16 @@ CFG_BUILTIN = { @@ -47,13 +47,16 @@ CFG_BUILTIN = {
], ],
'def_log_file': '/var/log/cloud-init.log', 'def_log_file': '/var/log/cloud-init.log',
'log_cfgs': [], 'log_cfgs': [],
@ -199,7 +209,7 @@ index ca4ffa8..3a04a58 100644
'vendor_data': {'enabled': True, 'prefix': []}, 'vendor_data': {'enabled': True, 'prefix': []},
diff --git a/rhel/README.rhel b/rhel/README.rhel diff --git a/rhel/README.rhel b/rhel/README.rhel
new file mode 100644 new file mode 100644
index 0000000..aa29630 index 00000000..aa29630d
--- /dev/null --- /dev/null
+++ b/rhel/README.rhel +++ b/rhel/README.rhel
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
@ -210,14 +220,14 @@ index 0000000..aa29630
+ - grub_dpkg + - grub_dpkg
diff --git a/rhel/cloud-init-tmpfiles.conf b/rhel/cloud-init-tmpfiles.conf diff --git a/rhel/cloud-init-tmpfiles.conf b/rhel/cloud-init-tmpfiles.conf
new file mode 100644 new file mode 100644
index 0000000..0c6d2a3 index 00000000..0c6d2a3b
--- /dev/null --- /dev/null
+++ b/rhel/cloud-init-tmpfiles.conf +++ b/rhel/cloud-init-tmpfiles.conf
@@ -0,0 +1 @@ @@ -0,0 +1 @@
+d /run/cloud-init 0700 root root - - +d /run/cloud-init 0700 root root - -
diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg
new file mode 100644 new file mode 100644
index 0000000..82e8bf6 index 00000000..82e8bf62
--- /dev/null --- /dev/null
+++ b/rhel/cloud.cfg +++ b/rhel/cloud.cfg
@@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
@ -292,7 +302,7 @@ index 0000000..82e8bf6
+# vim:syntax=yaml +# vim:syntax=yaml
diff --git a/rhel/systemd/cloud-config.service b/rhel/systemd/cloud-config.service diff --git a/rhel/systemd/cloud-config.service b/rhel/systemd/cloud-config.service
new file mode 100644 new file mode 100644
index 0000000..f3dcd4b index 00000000..f3dcd4be
--- /dev/null --- /dev/null
+++ b/rhel/systemd/cloud-config.service +++ b/rhel/systemd/cloud-config.service
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
@ -316,7 +326,7 @@ index 0000000..f3dcd4b
+WantedBy=cloud-init.target +WantedBy=cloud-init.target
diff --git a/rhel/systemd/cloud-config.target b/rhel/systemd/cloud-config.target diff --git a/rhel/systemd/cloud-config.target b/rhel/systemd/cloud-config.target
new file mode 100644 new file mode 100644
index 0000000..ae9b7d0 index 00000000..ae9b7d02
--- /dev/null --- /dev/null
+++ b/rhel/systemd/cloud-config.target +++ b/rhel/systemd/cloud-config.target
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
@ -333,10 +343,10 @@ index 0000000..ae9b7d0
+After=cloud-init-local.service cloud-init.service +After=cloud-init-local.service cloud-init.service
diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service
new file mode 100644 new file mode 100644
index 0000000..739b7e3 index 00000000..e281c0cf
--- /dev/null --- /dev/null
+++ b/rhel/systemd/cloud-final.service +++ b/rhel/systemd/cloud-final.service
@@ -0,0 +1,19 @@ @@ -0,0 +1,24 @@
+[Unit] +[Unit]
+Description=Execute cloud user/final scripts +Description=Execute cloud user/final scripts
+After=network-online.target cloud-config.service rc-local.service +After=network-online.target cloud-config.service rc-local.service
@ -350,6 +360,11 @@ index 0000000..739b7e3
+RemainAfterExit=yes +RemainAfterExit=yes
+TimeoutSec=0 +TimeoutSec=0
+KillMode=process +KillMode=process
+# Restart NetworkManager if it is present and running.
+ExecStartPost=/bin/sh -c 'u=NetworkManager.service; \
+ out=$(systemctl show --property=SubState $u) || exit; \
+ [ "$out" = "SubState=running" ] || exit 0; \
+ systemctl reload-or-try-restart $u'
+ +
+# Output needs to appear in instance console output +# Output needs to appear in instance console output
+StandardOutput=journal+console +StandardOutput=journal+console
@ -358,7 +373,7 @@ index 0000000..739b7e3
+WantedBy=cloud-init.target +WantedBy=cloud-init.target
diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service
new file mode 100644 new file mode 100644
index 0000000..8f9f6c9 index 00000000..8f9f6c9f
--- /dev/null --- /dev/null
+++ b/rhel/systemd/cloud-init-local.service +++ b/rhel/systemd/cloud-init-local.service
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
@ -395,7 +410,7 @@ index 0000000..8f9f6c9
+WantedBy=cloud-init.target +WantedBy=cloud-init.target
diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service
new file mode 100644 new file mode 100644
index 0000000..d0023a0 index 00000000..d0023a05
--- /dev/null --- /dev/null
+++ b/rhel/systemd/cloud-init.service +++ b/rhel/systemd/cloud-init.service
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
@ -426,7 +441,7 @@ index 0000000..d0023a0
+WantedBy=cloud-init.target +WantedBy=cloud-init.target
diff --git a/rhel/systemd/cloud-init.target b/rhel/systemd/cloud-init.target diff --git a/rhel/systemd/cloud-init.target b/rhel/systemd/cloud-init.target
new file mode 100644 new file mode 100644
index 0000000..083c3b6 index 00000000..083c3b6f
--- /dev/null --- /dev/null
+++ b/rhel/systemd/cloud-init.target +++ b/rhel/systemd/cloud-init.target
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
@ -438,7 +453,7 @@ index 0000000..083c3b6
+Description=Cloud-init target +Description=Cloud-init target
+After=multi-user.target +After=multi-user.target
diff --git a/setup.py b/setup.py diff --git a/setup.py b/setup.py
index cbacf48..d5cd01a 100755 index cbacf48e..d5cd01a4 100755
--- a/setup.py --- a/setup.py
+++ b/setup.py +++ b/setup.py
@@ -125,14 +125,6 @@ INITSYS_FILES = { @@ -125,14 +125,6 @@ INITSYS_FILES = {
@ -503,7 +518,7 @@ index cbacf48..d5cd01a 100755
'console_scripts': [ 'console_scripts': [
'cloud-init = cloudinit.cmd.main:main', 'cloud-init = cloudinit.cmd.main:main',
diff --git a/tools/read-version b/tools/read-version diff --git a/tools/read-version b/tools/read-version
index 02c9064..79755f7 100755 index 02c90643..79755f78 100755
--- a/tools/read-version --- a/tools/read-version
+++ b/tools/read-version +++ b/tools/read-version
@@ -71,32 +71,8 @@ version_long = None @@ -71,32 +71,8 @@ version_long = None
@ -542,5 +557,5 @@ index 02c9064..79755f7 100755
# version is X.Y.Z[+xxx.gHASH] # version is X.Y.Z[+xxx.gHASH]
# version_long is None or X.Y.Z-xxx-gHASH # version_long is None or X.Y.Z-xxx-gHASH
-- --
1.8.3.1 2.27.0

View File

@ -1,6 +1,6 @@
From d9024cd3bd3bf09b05eb75ba3d81bd15f519c9f8 Mon Sep 17 00:00:00 2001 From 472c2b5d4342b6ab6ce1584dc39bed0e6c1ca2e7 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com> From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Oct 2020 13:49:46 +0200 Date: Fri, 7 May 2021 13:36:06 +0200
Subject: Do not write NM_CONTROLLED=no in generated interface config files Subject: Do not write NM_CONTROLLED=no in generated interface config files
Conflicts 20.3: Conflicts 20.3:
@ -13,14 +13,14 @@ Signed-off-by: Eduardo Otubo <otubo@redhat.com>
Signed-off-by: Ryan McCabe <rmccabe@redhat.com> Signed-off-by: Ryan McCabe <rmccabe@redhat.com>
--- ---
cloudinit/net/sysconfig.py | 2 +- cloudinit/net/sysconfig.py | 2 +-
tests/unittests/test_net.py | 30 ------------------------------ tests/unittests/test_net.py | 28 ----------------------------
2 files changed, 1 insertion(+), 31 deletions(-) 2 files changed, 1 insertion(+), 29 deletions(-)
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 0a5d481..23e467d 100644 index 99a4bae4..3d276666 100644
--- a/cloudinit/net/sysconfig.py --- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py
@@ -277,7 +277,7 @@ class Renderer(renderer.Renderer): @@ -289,7 +289,7 @@ class Renderer(renderer.Renderer):
# details about this) # details about this)
iface_defaults = { iface_defaults = {
@ -30,7 +30,7 @@ index 0a5d481..23e467d 100644
'suse': {'BOOTPROTO': 'static', 'STARTMODE': 'auto'}, 'suse': {'BOOTPROTO': 'static', 'STARTMODE': 'auto'},
} }
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 54cc846..9985a97 100644 index 38d934d4..c67b5fcc 100644
--- a/tests/unittests/test_net.py --- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py
@@ -535,7 +535,6 @@ GATEWAY=172.19.3.254 @@ -535,7 +535,6 @@ GATEWAY=172.19.3.254
@ -49,15 +49,15 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -754,7 +752,6 @@ IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" @@ -756,7 +754,6 @@ IPV6_AUTOCONF=no
IPV6INIT=yes
IPV6_DEFAULTGW=2001:DB8::1 IPV6_DEFAULTGW=2001:DB8::1
IPV6_FORCE_ACCEPT_RA=no
NETMASK=255.255.252.0 NETMASK=255.255.252.0
-NM_CONTROLLED=no -NM_CONTROLLED=no
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -882,7 +879,6 @@ NETWORK_CONFIGS = { @@ -884,7 +881,6 @@ NETWORK_CONFIGS = {
BOOTPROTO=none BOOTPROTO=none
DEVICE=eth1 DEVICE=eth1
HWADDR=cf:d6:af:48:e8:80 HWADDR=cf:d6:af:48:e8:80
@ -65,7 +65,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no"""), USERCTL=no"""),
@@ -899,7 +895,6 @@ NETWORK_CONFIGS = { @@ -901,7 +897,6 @@ NETWORK_CONFIGS = {
IPADDR=192.168.21.3 IPADDR=192.168.21.3
NETMASK=255.255.255.0 NETMASK=255.255.255.0
METRIC=10000 METRIC=10000
@ -73,15 +73,15 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no"""), USERCTL=no"""),
@@ -1028,7 +1023,6 @@ NETWORK_CONFIGS = { @@ -1032,7 +1027,6 @@ NETWORK_CONFIGS = {
IPV6ADDR=2001:1::1/64 IPV6_AUTOCONF=no
IPV6INIT=yes IPV6_FORCE_ACCEPT_RA=no
NETMASK=255.255.255.0 NETMASK=255.255.255.0
- NM_CONTROLLED=no - NM_CONTROLLED=no
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -1622,7 +1616,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1737,7 +1731,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
DHCPV6C=yes DHCPV6C=yes
IPV6INIT=yes IPV6INIT=yes
MACADDR=aa:bb:cc:dd:ee:ff MACADDR=aa:bb:cc:dd:ee:ff
@ -89,15 +89,15 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Bond TYPE=Bond
USERCTL=no"""), USERCTL=no"""),
@@ -1630,7 +1623,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1745,7 +1738,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
BOOTPROTO=dhcp BOOTPROTO=dhcp
DEVICE=bond0.200 DEVICE=bond0.200
DHCLIENT_SET_DEFAULT_ROUTE=no DHCLIENT_SET_DEFAULT_ROUTE=no
- NM_CONTROLLED=no - NM_CONTROLLED=no
ONBOOT=yes ONBOOT=yes
PHYSDEV=bond0 PHYSDEV=bond0
TYPE=Ethernet USERCTL=no
@@ -1647,7 +1639,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1763,7 +1755,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
IPV6_DEFAULTGW=2001:4800:78ff:1b::1 IPV6_DEFAULTGW=2001:4800:78ff:1b::1
MACADDR=bb:bb:bb:bb:bb:aa MACADDR=bb:bb:bb:bb:bb:aa
NETMASK=255.255.255.0 NETMASK=255.255.255.0
@ -105,7 +105,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
PRIO=22 PRIO=22
STP=no STP=no
@@ -1657,7 +1648,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1773,7 +1764,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
BOOTPROTO=none BOOTPROTO=none
DEVICE=eth0 DEVICE=eth0
HWADDR=c0:d6:9f:2c:e8:80 HWADDR=c0:d6:9f:2c:e8:80
@ -113,15 +113,15 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no"""), USERCTL=no"""),
@@ -1674,7 +1664,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1790,7 +1780,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
MTU=1500 MTU=1500
NETMASK=255.255.255.0 NETMASK=255.255.255.0
NETMASK1=255.255.255.0 NETMASK1=255.255.255.0
- NM_CONTROLLED=no - NM_CONTROLLED=no
ONBOOT=yes ONBOOT=yes
PHYSDEV=eth0 PHYSDEV=eth0
TYPE=Ethernet USERCTL=no
@@ -1685,7 +1674,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1800,7 +1789,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
DEVICE=eth1 DEVICE=eth1
HWADDR=aa:d6:9f:2c:e8:80 HWADDR=aa:d6:9f:2c:e8:80
MASTER=bond0 MASTER=bond0
@ -129,7 +129,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
SLAVE=yes SLAVE=yes
TYPE=Ethernet TYPE=Ethernet
@@ -1695,7 +1683,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1810,7 +1798,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
DEVICE=eth2 DEVICE=eth2
HWADDR=c0:bb:9f:2c:e8:80 HWADDR=c0:bb:9f:2c:e8:80
MASTER=bond0 MASTER=bond0
@ -137,7 +137,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
SLAVE=yes SLAVE=yes
TYPE=Ethernet TYPE=Ethernet
@@ -1705,7 +1692,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1820,7 +1807,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
BRIDGE=br0 BRIDGE=br0
DEVICE=eth3 DEVICE=eth3
HWADDR=66:bb:9f:2c:e8:80 HWADDR=66:bb:9f:2c:e8:80
@ -145,7 +145,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no"""), USERCTL=no"""),
@@ -1714,7 +1700,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1829,7 +1815,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
BRIDGE=br0 BRIDGE=br0
DEVICE=eth4 DEVICE=eth4
HWADDR=98:bb:9f:2c:e8:80 HWADDR=98:bb:9f:2c:e8:80
@ -153,7 +153,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no"""), USERCTL=no"""),
@@ -1723,7 +1708,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1838,7 +1823,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
DEVICE=eth5 DEVICE=eth5
DHCLIENT_SET_DEFAULT_ROUTE=no DHCLIENT_SET_DEFAULT_ROUTE=no
HWADDR=98:bb:9f:2c:e8:8a HWADDR=98:bb:9f:2c:e8:8a
@ -161,7 +161,7 @@ index 54cc846..9985a97 100644
ONBOOT=no ONBOOT=no
TYPE=Ethernet TYPE=Ethernet
USERCTL=no"""), USERCTL=no"""),
@@ -2177,7 +2161,6 @@ iface bond0 inet6 static @@ -2294,7 +2278,6 @@ iface bond0 inet6 static
MTU=9000 MTU=9000
NETMASK=255.255.255.0 NETMASK=255.255.255.0
NETMASK1=255.255.255.0 NETMASK1=255.255.255.0
@ -169,7 +169,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Bond TYPE=Bond
USERCTL=no USERCTL=no
@@ -2187,7 +2170,6 @@ iface bond0 inet6 static @@ -2304,7 +2287,6 @@ iface bond0 inet6 static
DEVICE=bond0s0 DEVICE=bond0s0
HWADDR=aa:bb:cc:dd:e8:00 HWADDR=aa:bb:cc:dd:e8:00
MASTER=bond0 MASTER=bond0
@ -177,7 +177,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
SLAVE=yes SLAVE=yes
TYPE=Ethernet TYPE=Ethernet
@@ -2209,7 +2191,6 @@ iface bond0 inet6 static @@ -2326,7 +2308,6 @@ iface bond0 inet6 static
DEVICE=bond0s1 DEVICE=bond0s1
HWADDR=aa:bb:cc:dd:e8:01 HWADDR=aa:bb:cc:dd:e8:01
MASTER=bond0 MASTER=bond0
@ -185,7 +185,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
SLAVE=yes SLAVE=yes
TYPE=Ethernet TYPE=Ethernet
@@ -2266,7 +2247,6 @@ iface bond0 inet6 static @@ -2383,7 +2364,6 @@ iface bond0 inet6 static
BOOTPROTO=none BOOTPROTO=none
DEVICE=en0 DEVICE=en0
HWADDR=aa:bb:cc:dd:e8:00 HWADDR=aa:bb:cc:dd:e8:00
@ -193,15 +193,15 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no"""), USERCTL=no"""),
@@ -2283,7 +2263,6 @@ iface bond0 inet6 static @@ -2402,7 +2382,6 @@ iface bond0 inet6 static
MTU=2222 MTU=2222
NETMASK=255.255.255.0 NETMASK=255.255.255.0
NETMASK1=255.255.255.0 NETMASK1=255.255.255.0
- NM_CONTROLLED=no - NM_CONTROLLED=no
ONBOOT=yes ONBOOT=yes
PHYSDEV=en0 PHYSDEV=en0
TYPE=Ethernet USERCTL=no
@@ -2349,7 +2328,6 @@ iface bond0 inet6 static @@ -2467,7 +2446,6 @@ iface bond0 inet6 static
DEVICE=br0 DEVICE=br0
IPADDR=192.168.2.2 IPADDR=192.168.2.2
NETMASK=255.255.255.0 NETMASK=255.255.255.0
@ -209,23 +209,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
PRIO=22 PRIO=22
STP=no STP=no
@@ -2363,7 +2341,6 @@ iface bond0 inet6 static @@ -2591,7 +2569,6 @@ iface bond0 inet6 static
HWADDR=52:54:00:12:34:00
IPV6ADDR=2001:1::100/96
IPV6INIT=yes
- NM_CONTROLLED=no
ONBOOT=yes
TYPE=Ethernet
USERCTL=no
@@ -2375,7 +2352,6 @@ iface bond0 inet6 static
HWADDR=52:54:00:12:34:01
IPV6ADDR=2001:1::101/96
IPV6INIT=yes
- NM_CONTROLLED=no
ONBOOT=yes
TYPE=Ethernet
USERCTL=no
@@ -2469,7 +2445,6 @@ iface bond0 inet6 static
HWADDR=52:54:00:12:34:00 HWADDR=52:54:00:12:34:00
IPADDR=192.168.1.2 IPADDR=192.168.1.2
NETMASK=255.255.255.0 NETMASK=255.255.255.0
@ -233,7 +217,7 @@ index 54cc846..9985a97 100644
ONBOOT=no ONBOOT=no
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -2479,7 +2454,6 @@ iface bond0 inet6 static @@ -2601,7 +2578,6 @@ iface bond0 inet6 static
DEVICE=eth1 DEVICE=eth1
HWADDR=52:54:00:12:34:aa HWADDR=52:54:00:12:34:aa
MTU=1480 MTU=1480
@ -241,7 +225,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -2488,7 +2462,6 @@ iface bond0 inet6 static @@ -2610,7 +2586,6 @@ iface bond0 inet6 static
BOOTPROTO=none BOOTPROTO=none
DEVICE=eth2 DEVICE=eth2
HWADDR=52:54:00:12:34:ff HWADDR=52:54:00:12:34:ff
@ -249,7 +233,7 @@ index 54cc846..9985a97 100644
ONBOOT=no ONBOOT=no
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -2905,7 +2878,6 @@ class TestRhelSysConfigRendering(CiTestCase): @@ -3027,7 +3002,6 @@ class TestRhelSysConfigRendering(CiTestCase):
BOOTPROTO=dhcp BOOTPROTO=dhcp
DEVICE=eth1000 DEVICE=eth1000
HWADDR=07-1c-c6-75-a4-be HWADDR=07-1c-c6-75-a4-be
@ -257,7 +241,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -3026,7 +2998,6 @@ GATEWAY=10.0.2.2 @@ -3148,7 +3122,6 @@ GATEWAY=10.0.2.2
HWADDR=52:54:00:12:34:00 HWADDR=52:54:00:12:34:00
IPADDR=10.0.2.15 IPADDR=10.0.2.15
NETMASK=255.255.255.0 NETMASK=255.255.255.0
@ -265,7 +249,7 @@ index 54cc846..9985a97 100644
ONBOOT=yes ONBOOT=yes
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
@@ -3096,7 +3067,6 @@ USERCTL=no @@ -3218,7 +3191,6 @@ USERCTL=no
# #
BOOTPROTO=dhcp BOOTPROTO=dhcp
DEVICE=eth0 DEVICE=eth0
@ -274,5 +258,5 @@ index 54cc846..9985a97 100644
TYPE=Ethernet TYPE=Ethernet
USERCTL=no USERCTL=no
-- --
1.8.3.1 2.27.0

View File

@ -1,6 +1,6 @@
From de22eafc9046b8ea6fddda7440df5a05f5a40607 Mon Sep 17 00:00:00 2001 From 6134624f10ef56534e37624adc12f11b09910591 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com> From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Oct 2020 13:49:53 +0200 Date: Fri, 7 May 2021 13:36:08 +0200
Subject: limit permissions on def_log_file Subject: limit permissions on def_log_file
This sets a default mode of 0600 on def_log_file, and makes this This sets a default mode of 0600 on def_log_file, and makes this
@ -10,18 +10,22 @@ LP: #1541196
Resolves: rhbz#1424612 Resolves: rhbz#1424612
X-approved-upstream: true X-approved-upstream: true
Conflicts 21.1:
cloudinit/stages.py: adjusting call of ensure_file() to use more
recent version
Signed-off-by: Eduardo Otubo <otubo@redhat.com> Signed-off-by: Eduardo Otubo <otubo@redhat.com>
--- ---
cloudinit/settings.py | 1 + cloudinit/settings.py | 1 +
cloudinit/stages.py | 3 ++- cloudinit/stages.py | 1 +
doc/examples/cloud-config.txt | 4 ++++ doc/examples/cloud-config.txt | 4 ++++
3 files changed, 7 insertions(+), 1 deletion(-) 3 files changed, 6 insertions(+)
diff --git a/cloudinit/settings.py b/cloudinit/settings.py diff --git a/cloudinit/settings.py b/cloudinit/settings.py
index 3a04a58..439eee0 100644 index e690c0fd..43a1490c 100644
--- a/cloudinit/settings.py --- a/cloudinit/settings.py
+++ b/cloudinit/settings.py +++ b/cloudinit/settings.py
@@ -45,6 +45,7 @@ CFG_BUILTIN = { @@ -46,6 +46,7 @@ CFG_BUILTIN = {
'None', 'None',
], ],
'def_log_file': '/var/log/cloud-init.log', 'def_log_file': '/var/log/cloud-init.log',
@ -30,22 +34,19 @@ index 3a04a58..439eee0 100644
'mount_default_fields': [None, None, 'auto', 'defaults,nofail', '0', '2'], 'mount_default_fields': [None, None, 'auto', 'defaults,nofail', '0', '2'],
'ssh_deletekeys': False, 'ssh_deletekeys': False,
diff --git a/cloudinit/stages.py b/cloudinit/stages.py diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index 765f4aa..d769375 100644 index 3ef4491c..83e25dd1 100644
--- a/cloudinit/stages.py --- a/cloudinit/stages.py
+++ b/cloudinit/stages.py +++ b/cloudinit/stages.py
@@ -147,8 +147,9 @@ class Init(object): @@ -147,6 +147,7 @@ class Init(object):
def _initialize_filesystem(self): def _initialize_filesystem(self):
util.ensure_dirs(self._initial_subdirs()) util.ensure_dirs(self._initial_subdirs())
log_file = util.get_cfg_option_str(self.cfg, 'def_log_file') log_file = util.get_cfg_option_str(self.cfg, 'def_log_file')
+ log_file_mode = util.get_cfg_option_int(self.cfg, 'def_log_file_mode') + log_file_mode = util.get_cfg_option_int(self.cfg, 'def_log_file_mode')
if log_file: if log_file:
- util.ensure_file(log_file) util.ensure_file(log_file, preserve_mode=True)
+ util.ensure_file(log_file, mode=log_file_mode)
perms = self.cfg.get('syslog_fix_perms') perms = self.cfg.get('syslog_fix_perms')
if not perms:
perms = {}
diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt
index f3ae5e6..b5b1fdd 100644 index de9a0f87..bb33ad45 100644
--- a/doc/examples/cloud-config.txt --- a/doc/examples/cloud-config.txt
+++ b/doc/examples/cloud-config.txt +++ b/doc/examples/cloud-config.txt
@@ -414,10 +414,14 @@ timezone: US/Eastern @@ -414,10 +414,14 @@ timezone: US/Eastern
@ -64,5 +65,5 @@ index f3ae5e6..b5b1fdd 100644
# you can set passwords for a user or multiple users # you can set passwords for a user or multiple users
-- --
1.8.3.1 2.27.0

View File

@ -1,6 +1,6 @@
From bb87d9a83ddbc5bf84fbdab9c58dedc0c9629eea Mon Sep 17 00:00:00 2001 From 699d37a6ff3e343e214943794aac09e4156c2b2b Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com> From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Oct 2020 13:51:34 +0200 Date: Fri, 7 May 2021 13:36:10 +0200
Subject: sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp Subject: sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp
Don't write BOOTPROTO=dhcp for ipv6 dhcp, as BOOTPROTO applies Don't write BOOTPROTO=dhcp for ipv6 dhcp, as BOOTPROTO applies
@ -20,10 +20,10 @@ Signed-off-by: Eduardo Otubo <otubo@redhat.com>
1 file changed, 1 insertion(+) 1 file changed, 1 insertion(+)
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 9985a97..2cc57fe 100644 index c67b5fcc..4ea0e597 100644
--- a/tests/unittests/test_net.py --- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py
@@ -1614,6 +1614,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1729,6 +1729,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
BOOTPROTO=none BOOTPROTO=none
DEVICE=bond0 DEVICE=bond0
DHCPV6C=yes DHCPV6C=yes
@ -32,5 +32,5 @@ index 9985a97..2cc57fe 100644
MACADDR=aa:bb:cc:dd:ee:ff MACADDR=aa:bb:cc:dd:ee:ff
ONBOOT=yes ONBOOT=yes
-- --
1.8.3.1 2.27.0

View File

@ -1,6 +1,6 @@
From 9c6562c6d3516df8d11aa7cf7cd9cc62e5c91a70 Mon Sep 17 00:00:00 2001 From ccc75c1be3ae08d813193071c798fc905b5c03e5 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com> From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Oct 2020 13:51:37 +0200 Date: Fri, 7 May 2021 13:36:12 +0200
Subject: DataSourceAzure.py: use hostnamectl to set hostname Subject: DataSourceAzure.py: use hostnamectl to set hostname
RH-Author: Vitaly Kuznetsov <vkuznets@redhat.com> RH-Author: Vitaly Kuznetsov <vkuznets@redhat.com>
@ -40,10 +40,10 @@ Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
1 file changed, 1 insertion(+), 1 deletion(-) 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index f3c6452..1c214db 100755 index cee630f7..553b5a7e 100755
--- a/cloudinit/sources/DataSourceAzure.py --- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py +++ b/cloudinit/sources/DataSourceAzure.py
@@ -258,7 +258,7 @@ def get_hostname(hostname_command='hostname'): @@ -296,7 +296,7 @@ def get_hostname(hostname_command='hostname'):
def set_hostname(hostname, hostname_command='hostname'): def set_hostname(hostname, hostname_command='hostname'):
@ -53,5 +53,5 @@ index f3c6452..1c214db 100755
@azure_ds_telemetry_reporter @azure_ds_telemetry_reporter
-- --
1.8.3.1 2.27.0

View File

@ -1,6 +1,6 @@
From bdcad981ac530277529d1c77fb5e9e6f89409bd8 Mon Sep 17 00:00:00 2001 From dfea0490b899804761fbd7aa23822783d7c36ec5 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com> From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Oct 2020 13:51:44 +0200 Date: Fri, 7 May 2021 13:36:13 +0200
Subject: include 'NOZEROCONF=yes' in /etc/sysconfig/network Subject: include 'NOZEROCONF=yes' in /etc/sysconfig/network
RH-Author: Eduardo Otubo <otubo@redhat.com> RH-Author: Eduardo Otubo <otubo@redhat.com>
@ -27,10 +27,10 @@ Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
2 files changed, 10 insertions(+), 2 deletions(-) 2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 23e467d..af093dd 100644 index 3d276666..d5440998 100644
--- a/cloudinit/net/sysconfig.py --- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py
@@ -888,7 +888,16 @@ class Renderer(renderer.Renderer): @@ -925,7 +925,16 @@ class Renderer(renderer.Renderer):
# Distros configuring /etc/sysconfig/network as a file e.g. Centos # Distros configuring /etc/sysconfig/network as a file e.g. Centos
if sysconfig_path.endswith('network'): if sysconfig_path.endswith('network'):
util.ensure_dir(os.path.dirname(sysconfig_path)) util.ensure_dir(os.path.dirname(sysconfig_path))
@ -49,10 +49,10 @@ index 23e467d..af093dd 100644
netcfg.append('NETWORKING_IPV6=yes') netcfg.append('NETWORKING_IPV6=yes')
netcfg.append('IPV6_AUTOCONF=no') netcfg.append('IPV6_AUTOCONF=no')
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 2cc57fe..9985a97 100644 index 4ea0e597..c67b5fcc 100644
--- a/tests/unittests/test_net.py --- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py
@@ -1614,7 +1614,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true @@ -1729,7 +1729,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
BOOTPROTO=none BOOTPROTO=none
DEVICE=bond0 DEVICE=bond0
DHCPV6C=yes DHCPV6C=yes
@ -61,5 +61,5 @@ index 2cc57fe..9985a97 100644
MACADDR=aa:bb:cc:dd:ee:ff MACADDR=aa:bb:cc:dd:ee:ff
ONBOOT=yes ONBOOT=yes
-- --
1.8.3.1 2.27.0

View File

@ -1,6 +1,6 @@
From a52c7b659c6569c78aad4b92303f289009da476c Mon Sep 17 00:00:00 2001 From 24894dcf45a307f44e29dc5d5b2d864b75fd982c Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com> From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Oct 2020 13:51:50 +0200 Date: Fri, 7 May 2021 13:36:14 +0200
Subject: Remove race condition between cloud-init and NetworkManager Subject: Remove race condition between cloud-init and NetworkManager
Message-id: <20200302104635.11648-1-otubo@redhat.com> Message-id: <20200302104635.11648-1-otubo@redhat.com>
@ -115,12 +115,11 @@ Date: Thu May 28 08:44:06 2020 +0200
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com> Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
--- ---
rhel/cloud.cfg | 2 +- rhel/cloud.cfg | 2 +-
rhel/systemd/cloud-final.service | 2 ++
rhel/systemd/cloud-init.service | 1 + rhel/systemd/cloud-init.service | 1 +
3 files changed, 4 insertions(+), 1 deletion(-) 2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg
index 82e8bf6..9ecba21 100644 index 82e8bf62..9ecba215 100644
--- a/rhel/cloud.cfg --- a/rhel/cloud.cfg
+++ b/rhel/cloud.cfg +++ b/rhel/cloud.cfg
@@ -6,7 +6,7 @@ ssh_pwauth: 0 @@ -6,7 +6,7 @@ ssh_pwauth: 0
@ -132,21 +131,8 @@ index 82e8bf6..9ecba21 100644
ssh_genkeytypes: ~ ssh_genkeytypes: ~
syslog_fix_perms: ~ syslog_fix_perms: ~
disable_vmware_customization: false disable_vmware_customization: false
diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service
index 739b7e3..05add07 100644
--- a/rhel/systemd/cloud-final.service
+++ b/rhel/systemd/cloud-final.service
@@ -11,6 +11,8 @@ ExecStart=/usr/bin/cloud-init modules --mode=final
RemainAfterExit=yes
TimeoutSec=0
KillMode=process
+ExecStartPost=/bin/echo "trying to reload or restart NetworkManager.service"
+ExecStartPost=/usr/bin/systemctl try-reload-or-restart NetworkManager.service
# Output needs to appear in instance console output
StandardOutput=journal+console
diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service
index d0023a0..0b3d796 100644 index d0023a05..0b3d796d 100644
--- a/rhel/systemd/cloud-init.service --- a/rhel/systemd/cloud-init.service
+++ b/rhel/systemd/cloud-init.service +++ b/rhel/systemd/cloud-init.service
@@ -5,6 +5,7 @@ Wants=sshd-keygen.service @@ -5,6 +5,7 @@ Wants=sshd-keygen.service
@ -158,5 +144,5 @@ index d0023a0..0b3d796 100644
Before=sshd-keygen.service Before=sshd-keygen.service
Before=sshd.service Before=sshd.service
-- --
1.8.3.1 2.27.0

View File

@ -0,0 +1,496 @@
From b48dda73da94782d7ab0c455fa382d3a5ef3c419 Mon Sep 17 00:00:00 2001
From: Daniel Watkins <oddbloke@ubuntu.com>
Date: Mon, 8 Mar 2021 12:50:57 -0500
Subject: net: exclude OVS internal interfaces in get_interfaces (#829)
`get_interfaces` is used to in two ways, broadly: firstly, to determine
the available interfaces when converting cloud network configuration
formats to cloud-init's network configuration formats; and, secondly, to
ensure that any interfaces which are specified in network configuration
are (a) available, and (b) named correctly. The first of these is
unaffected by this commit, as no clouds support Open vSwitch
configuration in their network configuration formats.
For the second, we check that MAC addresses of physical devices are
unique. In some OVS configurations, there are OVS-created devices which
have duplicate MAC addresses, either with each other or with physical
devices. As these interfaces are created by OVS, we can be confident
that (a) they will be available when appropriate, and (b) that OVS will
name them correctly. As such, this commit excludes any OVS-internal
interfaces from the set of interfaces returned by `get_interfaces`.
LP: #1912844
---
cloudinit/net/__init__.py | 62 +++++++++
cloudinit/net/tests/test_init.py | 119 ++++++++++++++++++
.../sources/helpers/tests/test_openstack.py | 5 +
cloudinit/sources/tests/test_oracle.py | 4 +
.../integration_tests/bugs/test_lp1912844.py | 103 +++++++++++++++
.../test_datasource/test_configdrive.py | 8 ++
tests/unittests/test_net.py | 20 +++
7 files changed, 321 insertions(+)
create mode 100644 tests/integration_tests/bugs/test_lp1912844.py
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
index de65e7af..385b7bcc 100644
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -6,6 +6,7 @@
# This file is part of cloud-init. See LICENSE file for license information.
import errno
+import functools
import ipaddress
import logging
import os
@@ -19,6 +20,19 @@ from cloudinit.url_helper import UrlError, readurl
LOG = logging.getLogger(__name__)
SYS_CLASS_NET = "/sys/class/net/"
DEFAULT_PRIMARY_INTERFACE = 'eth0'
+OVS_INTERNAL_INTERFACE_LOOKUP_CMD = [
+ "ovs-vsctl",
+ "--format",
+ "csv",
+ "--no-headings",
+ "--timeout",
+ "10",
+ "--columns",
+ "name",
+ "find",
+ "interface",
+ "type=internal",
+]
def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
@@ -133,6 +147,52 @@ def master_is_openvswitch(devname):
return os.path.exists(ovs_path)
+@functools.lru_cache(maxsize=None)
+def openvswitch_is_installed() -> bool:
+ """Return a bool indicating if Open vSwitch is installed in the system."""
+ ret = bool(subp.which("ovs-vsctl"))
+ if not ret:
+ LOG.debug(
+ "ovs-vsctl not in PATH; not detecting Open vSwitch interfaces"
+ )
+ return ret
+
+
+@functools.lru_cache(maxsize=None)
+def get_ovs_internal_interfaces() -> list:
+ """Return a list of the names of OVS internal interfaces on the system.
+
+ These will all be strings, and are used to exclude OVS-specific interface
+ from cloud-init's network configuration handling.
+ """
+ try:
+ out, _err = subp.subp(OVS_INTERNAL_INTERFACE_LOOKUP_CMD)
+ except subp.ProcessExecutionError as exc:
+ if "database connection failed" in exc.stderr:
+ LOG.info(
+ "Open vSwitch is not yet up; no interfaces will be detected as"
+ " OVS-internal"
+ )
+ return []
+ raise
+ else:
+ return out.splitlines()
+
+
+def is_openvswitch_internal_interface(devname: str) -> bool:
+ """Returns True if this is an OVS internal interface.
+
+ If OVS is not installed or not yet running, this will return False.
+ """
+ if not openvswitch_is_installed():
+ return False
+ ovs_bridges = get_ovs_internal_interfaces()
+ if devname in ovs_bridges:
+ LOG.debug("Detected %s as an OVS interface", devname)
+ return True
+ return False
+
+
def is_netfailover(devname, driver=None):
""" netfailover driver uses 3 nics, master, primary and standby.
this returns True if the device is either the primary or standby
@@ -884,6 +944,8 @@ def get_interfaces(blacklist_drivers=None) -> list:
# skip nics that have no mac (00:00....)
if name != 'lo' and mac == zero_mac[:len(mac)]:
continue
+ if is_openvswitch_internal_interface(name):
+ continue
# skip nics that have drivers blacklisted
driver = device_driver(name)
if driver in blacklist_drivers:
diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py
index 0535387a..946f8ee2 100644
--- a/cloudinit/net/tests/test_init.py
+++ b/cloudinit/net/tests/test_init.py
@@ -391,6 +391,10 @@ class TestGetDeviceList(CiTestCase):
self.assertCountEqual(['eth0', 'eth1'], net.get_devicelist())
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False),
+)
class TestGetInterfaceMAC(CiTestCase):
def setUp(self):
@@ -1224,6 +1228,121 @@ class TestNetFailOver(CiTestCase):
self.assertFalse(net.is_netfailover(devname, driver))
+class TestOpenvswitchIsInstalled:
+ """Test cloudinit.net.openvswitch_is_installed.
+
+ Uses the ``clear_lru_cache`` local autouse fixture to allow us to test
+ despite the ``lru_cache`` decorator on the unit under test.
+ """
+
+ @pytest.fixture(autouse=True)
+ def clear_lru_cache(self):
+ net.openvswitch_is_installed.cache_clear()
+
+ @pytest.mark.parametrize(
+ "expected,which_return", [(True, "/some/path"), (False, None)]
+ )
+ @mock.patch("cloudinit.net.subp.which")
+ def test_mirrors_which_result(self, m_which, expected, which_return):
+ m_which.return_value = which_return
+ assert expected == net.openvswitch_is_installed()
+
+ @mock.patch("cloudinit.net.subp.which")
+ def test_only_calls_which_once(self, m_which):
+ net.openvswitch_is_installed()
+ net.openvswitch_is_installed()
+ assert 1 == m_which.call_count
+
+
+@mock.patch("cloudinit.net.subp.subp", return_value=("", ""))
+class TestGetOVSInternalInterfaces:
+ """Test cloudinit.net.get_ovs_internal_interfaces.
+
+ Uses the ``clear_lru_cache`` local autouse fixture to allow us to test
+ despite the ``lru_cache`` decorator on the unit under test.
+ """
+ @pytest.fixture(autouse=True)
+ def clear_lru_cache(self):
+ net.get_ovs_internal_interfaces.cache_clear()
+
+ def test_command_used(self, m_subp):
+ """Test we use the correct command when we call subp"""
+ net.get_ovs_internal_interfaces()
+
+ assert [
+ mock.call(net.OVS_INTERNAL_INTERFACE_LOOKUP_CMD)
+ ] == m_subp.call_args_list
+
+ def test_subp_contents_split_and_returned(self, m_subp):
+ """Test that the command output is appropriately mangled."""
+ stdout = "iface1\niface2\niface3\n"
+ m_subp.return_value = (stdout, "")
+
+ assert [
+ "iface1",
+ "iface2",
+ "iface3",
+ ] == net.get_ovs_internal_interfaces()
+
+ def test_database_connection_error_handled_gracefully(self, m_subp):
+ """Test that the error indicating OVS is down is handled gracefully."""
+ m_subp.side_effect = ProcessExecutionError(
+ stderr="database connection failed"
+ )
+
+ assert [] == net.get_ovs_internal_interfaces()
+
+ def test_other_errors_raised(self, m_subp):
+ """Test that only database connection errors are handled."""
+ m_subp.side_effect = ProcessExecutionError()
+
+ with pytest.raises(ProcessExecutionError):
+ net.get_ovs_internal_interfaces()
+
+ def test_only_runs_once(self, m_subp):
+ """Test that we cache the value."""
+ net.get_ovs_internal_interfaces()
+ net.get_ovs_internal_interfaces()
+
+ assert 1 == m_subp.call_count
+
+
+@mock.patch("cloudinit.net.get_ovs_internal_interfaces")
+@mock.patch("cloudinit.net.openvswitch_is_installed")
+class TestIsOpenVSwitchInternalInterface:
+ def test_false_if_ovs_not_installed(
+ self, m_openvswitch_is_installed, _m_get_ovs_internal_interfaces
+ ):
+ """Test that OVS' absence returns False."""
+ m_openvswitch_is_installed.return_value = False
+
+ assert not net.is_openvswitch_internal_interface("devname")
+
+ @pytest.mark.parametrize(
+ "detected_interfaces,devname,expected_return",
+ [
+ ([], "devname", False),
+ (["notdevname"], "devname", False),
+ (["devname"], "devname", True),
+ (["some", "other", "devices", "and", "ours"], "ours", True),
+ ],
+ )
+ def test_return_value_based_on_detected_interfaces(
+ self,
+ m_openvswitch_is_installed,
+ m_get_ovs_internal_interfaces,
+ detected_interfaces,
+ devname,
+ expected_return,
+ ):
+ """Test that the detected interfaces are used correctly."""
+ m_openvswitch_is_installed.return_value = True
+ m_get_ovs_internal_interfaces.return_value = detected_interfaces
+ assert expected_return == net.is_openvswitch_internal_interface(
+ devname
+ )
+
+
class TestIsIpAddress:
"""Tests for net.is_ip_address.
diff --git a/cloudinit/sources/helpers/tests/test_openstack.py b/cloudinit/sources/helpers/tests/test_openstack.py
index 2bde1e3f..95fb9743 100644
--- a/cloudinit/sources/helpers/tests/test_openstack.py
+++ b/cloudinit/sources/helpers/tests/test_openstack.py
@@ -1,10 +1,15 @@
# This file is part of cloud-init. See LICENSE file for license information.
# ./cloudinit/sources/helpers/tests/test_openstack.py
+from unittest import mock
from cloudinit.sources.helpers import openstack
from cloudinit.tests import helpers as test_helpers
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestConvertNetJson(test_helpers.CiTestCase):
def test_phy_types(self):
diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
index a7bbdfd9..dcf33b9b 100644
--- a/cloudinit/sources/tests/test_oracle.py
+++ b/cloudinit/sources/tests/test_oracle.py
@@ -173,6 +173,10 @@ class TestIsPlatformViable(test_helpers.CiTestCase):
m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestNetworkConfigFromOpcImds:
def test_no_secondary_nics_does_not_mutate_input(self, oracle_ds):
oracle_ds._vnics_data = [{}]
diff --git a/tests/integration_tests/bugs/test_lp1912844.py b/tests/integration_tests/bugs/test_lp1912844.py
new file mode 100644
index 00000000..efafae50
--- /dev/null
+++ b/tests/integration_tests/bugs/test_lp1912844.py
@@ -0,0 +1,103 @@
+"""Integration test for LP: #1912844
+
+cloud-init should ignore OVS-internal interfaces when performing its own
+interface determination: these interfaces are handled fully by OVS, so
+cloud-init should never need to touch them.
+
+This test is a semi-synthetic reproducer for the bug. It uses a similar
+network configuration, tweaked slightly to DHCP in a way that will succeed even
+on "failed" boots. The exact bug doesn't reproduce with the NoCloud
+datasource, because it runs at init-local time (whereas the MAAS datasource,
+from the report, runs only at init (network) time): this means that the
+networking code runs before OVS creates its interfaces (which happens after
+init-local but, of course, before networking is up), and so doesn't generate
+the traceback that they cause. We work around this by calling
+``get_interfaces_by_mac` directly in the test code.
+"""
+import pytest
+
+from tests.integration_tests import random_mac_address
+
+MAC_ADDRESS = random_mac_address()
+
+NETWORK_CONFIG = """\
+bonds:
+ bond0:
+ interfaces:
+ - enp5s0
+ macaddress: {0}
+ mtu: 1500
+bridges:
+ ovs-br:
+ interfaces:
+ - bond0
+ macaddress: {0}
+ mtu: 1500
+ openvswitch: {{}}
+ dhcp4: true
+ethernets:
+ enp5s0:
+ mtu: 1500
+ set-name: enp5s0
+ match:
+ macaddress: {0}
+version: 2
+vlans:
+ ovs-br.100:
+ id: 100
+ link: ovs-br
+ mtu: 1500
+ ovs-br.200:
+ id: 200
+ link: ovs-br
+ mtu: 1500
+""".format(MAC_ADDRESS)
+
+
+SETUP_USER_DATA = """\
+#cloud-config
+packages:
+- openvswitch-switch
+"""
+
+
+@pytest.fixture
+def ovs_enabled_session_cloud(session_cloud):
+ """A session_cloud wrapper, to use an OVS-enabled image for tests.
+
+ This implementation is complicated by wanting to use ``session_cloud``s
+ snapshot cleanup/retention logic, to avoid having to reimplement that here.
+ """
+ old_snapshot_id = session_cloud.snapshot_id
+ with session_cloud.launch(
+ user_data=SETUP_USER_DATA,
+ ) as instance:
+ instance.instance.clean()
+ session_cloud.snapshot_id = instance.snapshot()
+
+ yield session_cloud
+
+ try:
+ session_cloud.delete_snapshot()
+ finally:
+ session_cloud.snapshot_id = old_snapshot_id
+
+
+@pytest.mark.lxd_vm
+def test_get_interfaces_by_mac_doesnt_traceback(ovs_enabled_session_cloud):
+ """Launch our OVS-enabled image and confirm the bug doesn't reproduce."""
+ launch_kwargs = {
+ "config_dict": {
+ "user.network-config": NETWORK_CONFIG,
+ "volatile.eth0.hwaddr": MAC_ADDRESS,
+ },
+ }
+ with ovs_enabled_session_cloud.launch(
+ launch_kwargs=launch_kwargs,
+ ) as client:
+ result = client.execute(
+ "python3 -c"
+ "'from cloudinit.net import get_interfaces_by_mac;"
+ "get_interfaces_by_mac()'"
+ )
+ assert result.ok
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
index 6f830cc6..2e2b7847 100644
--- a/tests/unittests/test_datasource/test_configdrive.py
+++ b/tests/unittests/test_datasource/test_configdrive.py
@@ -494,6 +494,10 @@ class TestConfigDriveDataSource(CiTestCase):
self.assertEqual('config-disk (/dev/anything)', cfg_ds.subplatform)
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestNetJson(CiTestCase):
def setUp(self):
super(TestNetJson, self).setUp()
@@ -654,6 +658,10 @@ class TestNetJson(CiTestCase):
self.assertEqual(out_data, conv_data)
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestConvertNetworkData(CiTestCase):
with_logs = True
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index c67b5fcc..14d3462f 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -2908,6 +2908,10 @@ iface eth1 inet dhcp
self.assertEqual(0, mock_settle.call_count)
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestRhelSysConfigRendering(CiTestCase):
with_logs = True
@@ -3592,6 +3596,10 @@ USERCTL=no
expected, self._render_and_read(network_config=v2data))
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestOpenSuseSysConfigRendering(CiTestCase):
with_logs = True
@@ -5009,6 +5017,10 @@ class TestNetRenderers(CiTestCase):
self.assertTrue(result)
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestGetInterfaces(CiTestCase):
_data = {'bonds': ['bond1'],
'bridges': ['bridge1'],
@@ -5158,6 +5170,10 @@ class TestInterfaceHasOwnMac(CiTestCase):
self.assertFalse(interface_has_own_mac("eth0"))
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestGetInterfacesByMac(CiTestCase):
_data = {'bonds': ['bond1'],
'bridges': ['bridge1'],
@@ -5314,6 +5330,10 @@ class TestInterfacesSorting(CiTestCase):
['enp0s3', 'enp0s8', 'enp0s13', 'enp1s2', 'enp2s0', 'enp2s3'])
+@mock.patch(
+ "cloudinit.net.is_openvswitch_internal_interface",
+ mock.Mock(return_value=False)
+)
class TestGetIBHwaddrsByInterface(CiTestCase):
_ib_addr = '80:00:00:28:fe:80:00:00:00:00:00:00:00:11:22:03:00:33:44:56'
--
2.27.0

View File

@ -0,0 +1,87 @@
From bec5fb60ffae3d1137c7261e5571c2751c5dda25 Mon Sep 17 00:00:00 2001
From: James Falcon <TheRealFalcon@users.noreply.github.com>
Date: Mon, 8 Mar 2021 14:09:47 -0600
Subject: Fix requiring device-number on EC2 derivatives (#836)
#342 (70dbccbb) introduced the ability to determine route-metrics based on
the `device-number` provided by the EC2 IMDS. Not all datasources that
subclass EC2 will have this attribute, so allow the old behavior if
`device-number` is not present.
LP: #1917875
---
cloudinit/sources/DataSourceEc2.py | 3 +-
.../unittests/test_datasource/test_aliyun.py | 30 +++++++++++++++++++
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 1930a509..a2105dc7 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -765,13 +765,14 @@ def convert_ec2_metadata_network_config(
netcfg['ethernets'][nic_name] = dev_config
return netcfg
# Apply network config for all nics and any secondary IPv4/v6 addresses
+ nic_idx = 0
for mac, nic_name in sorted(macs_to_nics.items()):
nic_metadata = macs_metadata.get(mac)
if not nic_metadata:
continue # Not a physical nic represented in metadata
# device-number is zero-indexed, we want it 1-indexed for the
# multiplication on the following line
- nic_idx = int(nic_metadata['device-number']) + 1
+ nic_idx = int(nic_metadata.get('device-number', nic_idx)) + 1
dhcp_override = {'route-metric': nic_idx * 100}
dev_config = {'dhcp4': True, 'dhcp4-overrides': dhcp_override,
'dhcp6': False,
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index eb2828d5..cab1ac2b 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -7,6 +7,7 @@ from unittest import mock
from cloudinit import helpers
from cloudinit.sources import DataSourceAliYun as ay
+from cloudinit.sources.DataSourceEc2 import convert_ec2_metadata_network_config
from cloudinit.tests import helpers as test_helpers
DEFAULT_METADATA = {
@@ -183,6 +184,35 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
self.assertEqual(ay.parse_public_keys(public_keys),
public_keys['key-pair-0']['openssh-key'])
+ def test_route_metric_calculated_without_device_number(self):
+ """Test that route-metric code works without `device-number`
+
+ `device-number` is part of EC2 metadata, but not supported on aliyun.
+ Attempting to access it will raise a KeyError.
+
+ LP: #1917875
+ """
+ netcfg = convert_ec2_metadata_network_config(
+ {"interfaces": {"macs": {
+ "06:17:04:d7:26:09": {
+ "interface-id": "eni-e44ef49e",
+ },
+ "06:17:04:d7:26:08": {
+ "interface-id": "eni-e44ef49f",
+ }
+ }}},
+ macs_to_nics={
+ '06:17:04:d7:26:09': 'eth0',
+ '06:17:04:d7:26:08': 'eth1',
+ }
+ )
+
+ met0 = netcfg['ethernets']['eth0']['dhcp4-overrides']['route-metric']
+ met1 = netcfg['ethernets']['eth1']['dhcp4-overrides']['route-metric']
+
+ # route-metric numbers should be 100 apart
+ assert 100 == abs(met0 - met1)
+
class TestIsAliYun(test_helpers.CiTestCase):
ALIYUN_PRODUCT = 'Alibaba Cloud ECS'
--
2.27.0

View File

@ -1,496 +0,0 @@
From c3a1b3a5d7abe51a1facbdae71aca4b2bca7d6aa Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Wed, 28 Oct 2020 20:43:33 +0100
Subject: [PATCH 2/3] Add config modules for controlling IBM PowerVM RMC.
(#584)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 12: Support for cloud-init config modules for PowerVM Hypervisor in Red Hat cloud-init
RH-Commit: [1/1] d175c3607a8d4f473573ba0ce42e0f311dbc31ed (eterrell/cloud-init)
RH-Bugzilla: 1886430
commit f99d4f96b00a9cfec1c721d364cbfd728674e5dc (upstream/master)
Author: Aman306 <45781773+Aman306@users.noreply.github.com>
Date: Wed Oct 28 23:36:09 2020 +0530
Add config modules for controlling IBM PowerVM RMC. (#584)
Reliable Scalable Cluster Technology (RSCT) is a set of software
components that together provide a comprehensive clustering
environment(RAS features) for IBM PowerVM based virtual machines. RSCT
includes the Resource Monitoring and Control (RMC) subsystem. RMC is a
generalized framework used for managing, monitoring, and manipulating
resources. RMC runs as a daemon process on individual machines and needs
creation of unique node id and restarts during VM boot.
LP: #1895979
Co-authored-by: Scott Moser <smoser@brickies.net>
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
cloudinit/config/cc_refresh_rmc_and_interface.py | 159 +++++++++++++++++++++
cloudinit/config/cc_reset_rmc.py | 143 ++++++++++++++++++
config/cloud.cfg.tmpl | 2 +
.../test_handler_refresh_rmc_and_interface.py | 109 ++++++++++++++
tools/.github-cla-signers | 1 +
5 files changed, 414 insertions(+)
create mode 100644 cloudinit/config/cc_refresh_rmc_and_interface.py
create mode 100644 cloudinit/config/cc_reset_rmc.py
create mode 100644 tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py
diff --git a/cloudinit/config/cc_refresh_rmc_and_interface.py b/cloudinit/config/cc_refresh_rmc_and_interface.py
new file mode 100644
index 0000000..146758a
--- /dev/null
+++ b/cloudinit/config/cc_refresh_rmc_and_interface.py
@@ -0,0 +1,159 @@
+# (c) Copyright IBM Corp. 2020 All Rights Reserved
+#
+# Author: Aman Kumar Sinha <amansi26@in.ibm.com>
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""
+Refresh IPv6 interface and RMC
+------------------------------
+**Summary:** Ensure Network Manager is not managing IPv6 interface
+
+This module is IBM PowerVM Hypervisor specific
+
+Reliable Scalable Cluster Technology (RSCT) is a set of software components
+that together provide a comprehensive clustering environment(RAS features)
+for IBM PowerVM based virtual machines. RSCT includes the Resource
+Monitoring and Control (RMC) subsystem. RMC is a generalized framework used
+for managing, monitoring, and manipulating resources. RMC runs as a daemon
+process on individual machines and needs creation of unique node id and
+restarts during VM boot.
+More details refer
+https://www.ibm.com/support/knowledgecenter/en/SGVKBA_3.2/admin/bl503_ovrv.htm
+
+This module handles
+- Refreshing RMC
+- Disabling NetworkManager from handling IPv6 interface, as IPv6 interface
+ is used for communication between RMC daemon and PowerVM hypervisor.
+
+**Internal name:** ``cc_refresh_rmc_and_interface``
+
+**Module frequency:** per always
+
+**Supported distros:** RHEL
+
+"""
+
+from cloudinit import log as logging
+from cloudinit.settings import PER_ALWAYS
+from cloudinit import util
+from cloudinit import subp
+from cloudinit import netinfo
+
+import errno
+
+frequency = PER_ALWAYS
+
+LOG = logging.getLogger(__name__)
+# Ensure that /opt/rsct/bin has been added to standard PATH of the
+# distro. The symlink to rmcctrl is /usr/sbin/rsct/bin/rmcctrl .
+RMCCTRL = 'rmcctrl'
+
+
+def handle(name, _cfg, _cloud, _log, _args):
+ if not subp.which(RMCCTRL):
+ LOG.debug("No '%s' in path, disabled", RMCCTRL)
+ return
+
+ LOG.debug(
+ 'Making the IPv6 up explicitly. '
+ 'Ensuring IPv6 interface is not being handled by NetworkManager '
+ 'and it is restarted to re-establish the communication with '
+ 'the hypervisor')
+
+ ifaces = find_ipv6_ifaces()
+
+ # Setting NM_CONTROLLED=no for IPv6 interface
+ # making it down and up
+
+ if len(ifaces) == 0:
+ LOG.debug("Did not find any interfaces with ipv6 addresses.")
+ else:
+ for iface in ifaces:
+ refresh_ipv6(iface)
+ disable_ipv6(sysconfig_path(iface))
+ restart_network_manager()
+
+
+def find_ipv6_ifaces():
+ info = netinfo.netdev_info()
+ ifaces = []
+ for iface, data in info.items():
+ if iface == "lo":
+ LOG.debug('Skipping localhost interface')
+ if len(data.get("ipv4", [])) != 0:
+ # skip this interface, as it has ipv4 addrs
+ continue
+ ifaces.append(iface)
+ return ifaces
+
+
+def refresh_ipv6(interface):
+ # IPv6 interface is explicitly brought up, subsequent to which the
+ # RMC services are restarted to re-establish the communication with
+ # the hypervisor.
+ subp.subp(['ip', 'link', 'set', interface, 'down'])
+ subp.subp(['ip', 'link', 'set', interface, 'up'])
+
+
+def sysconfig_path(iface):
+ return '/etc/sysconfig/network-scripts/ifcfg-' + iface
+
+
+def restart_network_manager():
+ subp.subp(['systemctl', 'restart', 'NetworkManager'])
+
+
+def disable_ipv6(iface_file):
+ # Ensuring that the communication b/w the hypervisor and VM is not
+ # interrupted due to NetworkManager. For this purpose, as part of
+ # this function, the NM_CONTROLLED is explicitly set to No for IPV6
+ # interface and NetworkManager is restarted.
+ try:
+ contents = util.load_file(iface_file)
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ LOG.debug("IPv6 interface file %s does not exist\n",
+ iface_file)
+ else:
+ raise e
+
+ if 'IPV6INIT' not in contents:
+ LOG.debug("Interface file %s did not have IPV6INIT", iface_file)
+ return
+
+ LOG.debug("Editing interface file %s ", iface_file)
+
+ # Dropping any NM_CONTROLLED or IPV6 lines from IPv6 interface file.
+ lines = contents.splitlines()
+ lines = [line for line in lines if not search(line)]
+ lines.append("NM_CONTROLLED=no")
+
+ with open(iface_file, "w") as fp:
+ fp.write("\n".join(lines) + "\n")
+
+
+def search(contents):
+ # Search for any NM_CONTROLLED or IPV6 lines in IPv6 interface file.
+ return(
+ contents.startswith("IPV6ADDR") or
+ contents.startswith("IPADDR6") or
+ contents.startswith("IPV6INIT") or
+ contents.startswith("NM_CONTROLLED"))
+
+
+def refresh_rmc():
+ # To make a healthy connection between RMC daemon and hypervisor we
+ # refresh RMC. With refreshing RMC we are ensuring that making IPv6
+ # down and up shouldn't impact communication between RMC daemon and
+ # hypervisor.
+ # -z : stop Resource Monitoring & Control subsystem and all resource
+ # managers, but the command does not return control to the user
+ # until the subsystem and all resource managers are stopped.
+ # -s : start Resource Monitoring & Control subsystem.
+ try:
+ subp.subp([RMCCTRL, '-z'])
+ subp.subp([RMCCTRL, '-s'])
+ except Exception:
+ util.logexc(LOG, 'Failed to refresh the RMC subsystem.')
+ raise
diff --git a/cloudinit/config/cc_reset_rmc.py b/cloudinit/config/cc_reset_rmc.py
new file mode 100644
index 0000000..1cd7277
--- /dev/null
+++ b/cloudinit/config/cc_reset_rmc.py
@@ -0,0 +1,143 @@
+# (c) Copyright IBM Corp. 2020 All Rights Reserved
+#
+# Author: Aman Kumar Sinha <amansi26@in.ibm.com>
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+
+"""
+Reset RMC
+------------
+**Summary:** reset rsct node id
+
+Reset RMC module is IBM PowerVM Hypervisor specific
+
+Reliable Scalable Cluster Technology (RSCT) is a set of software components,
+that together provide a comprehensive clustering environment (RAS features)
+for IBM PowerVM based virtual machines. RSCT includes the Resource monitoring
+and control (RMC) subsystem. RMC is a generalized framework used for managing,
+monitoring, and manipulating resources. RMC runs as a daemon process on
+individual machines and needs creation of unique node id and restarts
+during VM boot.
+More details refer
+https://www.ibm.com/support/knowledgecenter/en/SGVKBA_3.2/admin/bl503_ovrv.htm
+
+This module handles
+- creation of the unique RSCT node id to every instance/virtual machine
+ and ensure once set, it isn't changed subsequently by cloud-init.
+ In order to do so, it restarts RSCT service.
+
+Prerequisite of using this module is to install RSCT packages.
+
+**Internal name:** ``cc_reset_rmc``
+
+**Module frequency:** per instance
+
+**Supported distros:** rhel, sles and ubuntu
+
+"""
+import os
+
+from cloudinit import log as logging
+from cloudinit.settings import PER_INSTANCE
+from cloudinit import util
+from cloudinit import subp
+
+frequency = PER_INSTANCE
+
+# RMCCTRL is expected to be in system PATH (/opt/rsct/bin)
+# The symlink for RMCCTRL and RECFGCT are
+# /usr/sbin/rsct/bin/rmcctrl and
+# /usr/sbin/rsct/install/bin/recfgct respectively.
+RSCT_PATH = '/opt/rsct/install/bin'
+RMCCTRL = 'rmcctrl'
+RECFGCT = 'recfgct'
+
+LOG = logging.getLogger(__name__)
+
+NODE_ID_FILE = '/etc/ct_node_id'
+
+
+def handle(name, _cfg, cloud, _log, _args):
+ # Ensuring node id has to be generated only once during first boot
+ if cloud.datasource.platform_type == 'none':
+ LOG.debug('Skipping creation of new ct_node_id node')
+ return
+
+ if not os.path.isdir(RSCT_PATH):
+ LOG.debug("module disabled, RSCT_PATH not present")
+ return
+
+ orig_path = os.environ.get('PATH')
+ try:
+ add_path(orig_path)
+ reset_rmc()
+ finally:
+ if orig_path:
+ os.environ['PATH'] = orig_path
+ else:
+ del os.environ['PATH']
+
+
+def reconfigure_rsct_subsystems():
+ # Reconfigure the RSCT subsystems, which includes removing all RSCT data
+ # under the /var/ct directory, generating a new node ID, and making it
+ # appear as if the RSCT components were just installed
+ try:
+ out = subp.subp([RECFGCT])[0]
+ LOG.debug(out.strip())
+ return out
+ except subp.ProcessExecutionError:
+ util.logexc(LOG, 'Failed to reconfigure the RSCT subsystems.')
+ raise
+
+
+def get_node_id():
+ try:
+ fp = util.load_file(NODE_ID_FILE)
+ node_id = fp.split('\n')[0]
+ return node_id
+ except Exception:
+ util.logexc(LOG, 'Failed to get node ID from file %s.' % NODE_ID_FILE)
+ raise
+
+
+def add_path(orig_path):
+ # Adding the RSCT_PATH to env standard path
+ # So thet cloud init automatically find and
+ # run RECFGCT to create new node_id.
+ suff = ":" + orig_path if orig_path else ""
+ os.environ['PATH'] = RSCT_PATH + suff
+ return os.environ['PATH']
+
+
+def rmcctrl():
+ # Stop the RMC subsystem and all resource managers so that we can make
+ # some changes to it
+ try:
+ return subp.subp([RMCCTRL, '-z'])
+ except Exception:
+ util.logexc(LOG, 'Failed to stop the RMC subsystem.')
+ raise
+
+
+def reset_rmc():
+ LOG.debug('Attempting to reset RMC.')
+
+ node_id_before = get_node_id()
+ LOG.debug('Node ID at beginning of module: %s', node_id_before)
+
+ # Stop the RMC subsystem and all resource managers so that we can make
+ # some changes to it
+ rmcctrl()
+ reconfigure_rsct_subsystems()
+
+ node_id_after = get_node_id()
+ LOG.debug('Node ID at end of module: %s', node_id_after)
+
+ # Check if new node ID is generated or not
+ # by comparing old and new node ID
+ if node_id_after == node_id_before:
+ msg = 'New node ID did not get generated.'
+ LOG.error(msg)
+ raise Exception(msg)
diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
index 2beb9b0..7171aaa 100644
--- a/config/cloud.cfg.tmpl
+++ b/config/cloud.cfg.tmpl
@@ -135,6 +135,8 @@ cloud_final_modules:
- chef
- mcollective
- salt-minion
+ - reset_rmc
+ - refresh_rmc_and_interface
- rightscale_userdata
- scripts-vendor
- scripts-per-once
diff --git a/tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py b/tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py
new file mode 100644
index 0000000..e13b779
--- /dev/null
+++ b/tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py
@@ -0,0 +1,109 @@
+from cloudinit.config import cc_refresh_rmc_and_interface as ccrmci
+
+from cloudinit import util
+
+from cloudinit.tests import helpers as t_help
+from cloudinit.tests.helpers import mock
+
+from textwrap import dedent
+import logging
+
+LOG = logging.getLogger(__name__)
+MPATH = "cloudinit.config.cc_refresh_rmc_and_interface"
+NET_INFO = {
+ 'lo': {'ipv4': [{'ip': '127.0.0.1',
+ 'bcast': '', 'mask': '255.0.0.0',
+ 'scope': 'host'}],
+ 'ipv6': [{'ip': '::1/128',
+ 'scope6': 'host'}], 'hwaddr': '',
+ 'up': 'True'},
+ 'env2': {'ipv4': [{'ip': '8.0.0.19',
+ 'bcast': '8.0.0.255', 'mask': '255.255.255.0',
+ 'scope': 'global'}],
+ 'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8220/64',
+ 'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:20',
+ 'up': 'True'},
+ 'env3': {'ipv4': [{'ip': '90.0.0.14',
+ 'bcast': '90.0.0.255', 'mask': '255.255.255.0',
+ 'scope': 'global'}],
+ 'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8221/64',
+ 'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:21',
+ 'up': 'True'},
+ 'env4': {'ipv4': [{'ip': '9.114.23.7',
+ 'bcast': '9.114.23.255', 'mask': '255.255.255.0',
+ 'scope': 'global'}],
+ 'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8222/64',
+ 'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:22',
+ 'up': 'True'},
+ 'env5': {'ipv4': [],
+ 'ipv6': [{'ip': 'fe80::9c26:c3ff:fea4:62c8/64',
+ 'scope6': 'link'}], 'hwaddr': '42:20:86:df:fa:4c',
+ 'up': 'True'}}
+
+
+class TestRsctNodeFile(t_help.CiTestCase):
+ def test_disable_ipv6_interface(self):
+ """test parsing of iface files."""
+ fname = self.tmp_path("iface-eth5")
+ util.write_file(fname, dedent("""\
+ BOOTPROTO=static
+ DEVICE=eth5
+ HWADDR=42:20:86:df:fa:4c
+ IPV6INIT=yes
+ IPADDR6=fe80::9c26:c3ff:fea4:62c8/64
+ IPV6ADDR=fe80::9c26:c3ff:fea4:62c8/64
+ NM_CONTROLLED=yes
+ ONBOOT=yes
+ STARTMODE=auto
+ TYPE=Ethernet
+ USERCTL=no
+ """))
+
+ ccrmci.disable_ipv6(fname)
+ self.assertEqual(dedent("""\
+ BOOTPROTO=static
+ DEVICE=eth5
+ HWADDR=42:20:86:df:fa:4c
+ ONBOOT=yes
+ STARTMODE=auto
+ TYPE=Ethernet
+ USERCTL=no
+ NM_CONTROLLED=no
+ """), util.load_file(fname))
+
+ @mock.patch(MPATH + '.refresh_rmc')
+ @mock.patch(MPATH + '.restart_network_manager')
+ @mock.patch(MPATH + '.disable_ipv6')
+ @mock.patch(MPATH + '.refresh_ipv6')
+ @mock.patch(MPATH + '.netinfo.netdev_info')
+ @mock.patch(MPATH + '.subp.which')
+ def test_handle(self, m_refresh_rmc,
+ m_netdev_info, m_refresh_ipv6, m_disable_ipv6,
+ m_restart_nm, m_which):
+ """Basic test of handle."""
+ m_netdev_info.return_value = NET_INFO
+ m_which.return_value = '/opt/rsct/bin/rmcctrl'
+ ccrmci.handle(
+ "refresh_rmc_and_interface", None, None, None, None)
+ self.assertEqual(1, m_netdev_info.call_count)
+ m_refresh_ipv6.assert_called_with('env5')
+ m_disable_ipv6.assert_called_with(
+ '/etc/sysconfig/network-scripts/ifcfg-env5')
+ self.assertEqual(1, m_restart_nm.call_count)
+ self.assertEqual(1, m_refresh_rmc.call_count)
+
+ @mock.patch(MPATH + '.netinfo.netdev_info')
+ def test_find_ipv6(self, m_netdev_info):
+ """find_ipv6_ifaces parses netdev_info returning those with ipv6"""
+ m_netdev_info.return_value = NET_INFO
+ found = ccrmci.find_ipv6_ifaces()
+ self.assertEqual(['env5'], found)
+
+ @mock.patch(MPATH + '.subp.subp')
+ def test_refresh_ipv6(self, m_subp):
+ """refresh_ipv6 should ip down and up the interface."""
+ iface = "myeth0"
+ ccrmci.refresh_ipv6(iface)
+ m_subp.assert_has_calls([
+ mock.call(['ip', 'link', 'set', iface, 'down']),
+ mock.call(['ip', 'link', 'set', iface, 'up'])])
diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers
index c67db43..802a35b 100644
--- a/tools/.github-cla-signers
+++ b/tools/.github-cla-signers
@@ -1,4 +1,5 @@
AlexBaranowski
+Aman306
beezly
bipinbachhao
BirknerAlex
--
1.8.3.1

View File

@ -1,58 +0,0 @@
From 8a7d21fa739901bad847294004266dba76c027af Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Tue, 1 Dec 2020 15:51:47 +0100
Subject: [PATCH 2/4] Adding BOOTPROTO = dhcp to render sysconfig dhcp6
stateful on RHEL (#685)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 25: Adding BOOTPROTO = dhcp to render sysconfig dhcp6 stateful on RHEL (#685)
RH-Commit: [1/1] b7304323096b1e40287950e44cf7aa3cdb4ba99e (eterrell/cloud-init)
RH-Bugzilla: 1859695
BOOTPROTO needs to be set to 'dhcp' on RHEL so NetworkManager can
properly acquire ipv6 address.
rhbz: #1859695
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
Co-authored-by: Daniel Watkins <oddbloke@ubuntu.com>
Co-authored-by: Scott Moser <smoser@brickies.net>
---
cloudinit/net/sysconfig.py | 6 ++++++
tests/unittests/test_net.py | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 078636a4..94801a93 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -391,6 +391,12 @@ class Renderer(renderer.Renderer):
# Only IPv6 is DHCP, IPv4 may be static
iface_cfg['BOOTPROTO'] = 'dhcp6'
iface_cfg['DHCLIENT6_MODE'] = 'managed'
+ # only if rhel AND dhcpv6 stateful
+ elif (flavor == 'rhel' and
+ subnet_type == 'ipv6_dhcpv6-stateful'):
+ iface_cfg['BOOTPROTO'] = 'dhcp'
+ iface_cfg['DHCPV6C'] = True
+ iface_cfg['IPV6INIT'] = True
else:
iface_cfg['IPV6INIT'] = True
# Configure network settings using DHCPv6
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index c0337459..bcd261db 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1359,7 +1359,7 @@ NETWORK_CONFIGS = {
},
'expected_sysconfig_rhel': {
'ifcfg-iface0': textwrap.dedent("""\
- BOOTPROTO=none
+ BOOTPROTO=dhcp
DEVICE=iface0
DHCPV6C=yes
IPV6INIT=yes
--
2.18.4

View File

@ -1,60 +0,0 @@
From bcbd6be99d8317793aff905c4222c351a1bf5c46 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Thu, 21 Jan 2021 10:08:49 +0100
Subject: [PATCH 1/2] DataSourceAzure: update password for defuser if exists
(#671)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 37: DataSourceAzure: update password for defuser if exists (#671)
RH-Commit: [1/1] 264092a68a3771cc4ed99dad5b93f7a1433e143a (eterrell/cloud-init)
RH-Bugzilla: 1900892
commit eea754492f074e00b601cf77aa278e3623857c5a
Author: Anh Vo <anhvo@microsoft.com>
Date: Thu Nov 19 00:35:46 2020 -0500
DataSourceAzure: update password for defuser if exists (#671)
cc_set_password will only update the password for the default user if
cfg['password'] is set. The existing code of datasource Azure will fail
to update the default user's password because it does not set that
metadata. If the default user doesn't exist in the image, the current
code works fine because the password is set during user create and
not in cc_set_password
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
cloudinit/sources/DataSourceAzure.py | 2 +-
tests/unittests/test_datasource/test_azure.py | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 1c214db9..d4a2d60f 100755
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -1231,7 +1231,7 @@ def read_azure_ovf(contents):
if password:
defuser['lock_passwd'] = False
if DEF_PASSWD_REDACTION != password:
- defuser['passwd'] = encrypt_pass(password)
+ defuser['passwd'] = cfg['password'] = encrypt_pass(password)
if defuser:
cfg['system_info'] = {'default_user': defuser}
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 47e03bd1..2059990a 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -919,6 +919,9 @@ scbus-1 on xpt0 bus 0
crypt.crypt(odata['UserPassword'],
defuser['passwd'][0:pos]))
+ # the same hashed value should also be present in cfg['password']
+ self.assertEqual(defuser['passwd'], dsrc.cfg['password'])
+
def test_user_not_locked_if_password_redacted(self):
odata = {'HostName': "myhost", 'UserName': "myuser",
'UserPassword': dsaz.DEF_PASSWD_REDACTION}
--
2.18.4

View File

@ -1,295 +0,0 @@
From 5ded09d5acf4d653fe2cbd54814f53063d265489 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Thu, 29 Oct 2020 15:05:42 +0100
Subject: [PATCH 1/3] Explicit set IPV6_AUTOCONF and IPV6_FORCE_ACCEPT_RA on
static6 (#634)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 13: [RHEL-8.4.0] Add support for ipv6_autoconf on cloud-init-20.3
RH-Commit: [1/1] 41e61c35893f4487981a1ad31f9f97a9a740b397 (eterrell/cloud-init)
RH-Bugzilla: 1889635
commit b46e4a8cff667c8441622089cf7d57aeb88220cd
Author: Eduardo Otubo <otubo@redhat.com>
Date: Thu Oct 29 15:05:42 2020 +0100
Explicit set IPV6_AUTOCONF and IPV6_FORCE_ACCEPT_RA on static6 (#634)
The static and static6 subnet types for network_data.json were
being ignored by the Openstack handler, this would cause the code to
break and not function properly.
As of today, if a static6 configuration is chosen, the interface will
still eventually be available to receive router advertisements or be set
from NetworkManager to wait for them and cycle the interface in negative
case.
It is safe to assume that if the interface is manually configured to use
static ipv6 address, there's no need to wait for router advertisements.
This patch will set automatically IPV6_AUTOCONF and IPV6_FORCE_ACCEPT_RA
both to "no" in this case.
This patch fixes the specific behavior only for RHEL flavor and
sysconfig renderer. It also introduces new unit tests for the specific
case as well as adjusts some existent tests to be compatible with the
new options. This patch also addresses this problem by assigning the
appropriate subnet type for each case on the openstack handler.
rhbz: #1889635
rhbz: #1889635
Signed-off-by: Eduardo Otubo otubo@redhat.com
Signed-off-by: Eduardo Otubo otubo@redhat.com
---
cloudinit/net/network_state.py | 3 +-
cloudinit/net/sysconfig.py | 4 +
cloudinit/sources/helpers/openstack.py | 8 +-
tests/unittests/test_distros/test_netconfig.py | 2 +
tests/unittests/test_net.py | 100 +++++++++++++++++++++++++
5 files changed, 115 insertions(+), 2 deletions(-)
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index b2f7d31..d9e7fd5 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -820,7 +820,8 @@ def _normalize_subnet(subnet):
if subnet.get('type') in ('static', 'static6'):
normal_subnet.update(
- _normalize_net_keys(normal_subnet, address_keys=('address',)))
+ _normalize_net_keys(normal_subnet, address_keys=(
+ 'address', 'ip_address',)))
normal_subnet['routes'] = [_normalize_route(r)
for r in subnet.get('routes', [])]
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index af093dd..c078898 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -451,6 +451,10 @@ class Renderer(renderer.Renderer):
iface_cfg[mtu_key] = subnet['mtu']
else:
iface_cfg[mtu_key] = subnet['mtu']
+
+ if subnet_is_ipv6(subnet) and flavor == 'rhel':
+ iface_cfg['IPV6_FORCE_ACCEPT_RA'] = False
+ iface_cfg['IPV6_AUTOCONF'] = False
elif subnet_type == 'manual':
if flavor == 'suse':
LOG.debug('Unknown subnet type setting "%s"', subnet_type)
diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
index 65e020c..3e6365f 100644
--- a/cloudinit/sources/helpers/openstack.py
+++ b/cloudinit/sources/helpers/openstack.py
@@ -602,11 +602,17 @@ def convert_net_json(network_json=None, known_macs=None):
elif network['type'] in ['ipv6_slaac', 'ipv6_dhcpv6-stateless',
'ipv6_dhcpv6-stateful']:
subnet.update({'type': network['type']})
- elif network['type'] in ['ipv4', 'ipv6']:
+ elif network['type'] in ['ipv4', 'static']:
subnet.update({
'type': 'static',
'address': network.get('ip_address'),
})
+ elif network['type'] in ['ipv6', 'static6']:
+ cfg.update({'accept-ra': False})
+ subnet.update({
+ 'type': 'static6',
+ 'address': network.get('ip_address'),
+ })
# Enable accept_ra for stateful and legacy ipv6_dhcp types
if network['type'] in ['ipv6_dhcpv6-stateful', 'ipv6_dhcp']:
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 8d7b09c..f9fc3a1 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -514,7 +514,9 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
DEVICE=eth0
IPV6ADDR=2607:f0d0:1002:0011::2/64
IPV6INIT=yes
+ IPV6_AUTOCONF=no
IPV6_DEFAULTGW=2607:f0d0:1002:0011::1
+ IPV6_FORCE_ACCEPT_RA=no
NM_CONTROLLED=no
ONBOOT=yes
TYPE=Ethernet
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 9985a97..d7a7a65 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -750,7 +750,9 @@ IPADDR=172.19.1.34
IPV6ADDR=2001:DB8::10/64
IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64"
IPV6INIT=yes
+IPV6_AUTOCONF=no
IPV6_DEFAULTGW=2001:DB8::1
+IPV6_FORCE_ACCEPT_RA=no
NETMASK=255.255.252.0
ONBOOT=yes
TYPE=Ethernet
@@ -1022,6 +1024,8 @@ NETWORK_CONFIGS = {
IPADDR=192.168.14.2
IPV6ADDR=2001:1::1/64
IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
NETMASK=255.255.255.0
ONBOOT=yes
TYPE=Ethernet
@@ -1247,6 +1251,33 @@ NETWORK_CONFIGS = {
"""),
},
},
+ 'static6': {
+ 'yaml': textwrap.dedent("""\
+ version: 1
+ config:
+ - type: 'physical'
+ name: 'iface0'
+ accept-ra: 'no'
+ subnets:
+ - type: 'static6'
+ address: 2001:1::1/64
+ """).rstrip(' '),
+ 'expected_sysconfig_rhel': {
+ 'ifcfg-iface0': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=iface0
+ IPV6ADDR=2001:1::1/64
+ IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
+ DEVICE=iface0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ },
+ },
'dhcpv6_stateless': {
'expected_eni': textwrap.dedent("""\
auto lo
@@ -1636,6 +1667,8 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
IPADDR=192.168.14.2
IPV6ADDR=2001:1::1/64
IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
IPV6_DEFAULTGW=2001:4800:78ff:1b::1
MACADDR=bb:bb:bb:bb:bb:aa
NETMASK=255.255.255.0
@@ -2158,6 +2191,8 @@ iface bond0 inet6 static
IPADDR1=192.168.1.2
IPV6ADDR=2001:1::1/92
IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
MTU=9000
NETMASK=255.255.255.0
NETMASK1=255.255.255.0
@@ -2259,6 +2294,8 @@ iface bond0 inet6 static
IPADDR1=192.168.1.2
IPV6ADDR=2001:1::bbbb/96
IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
IPV6_DEFAULTGW=2001:1::1
MTU=2222
NETMASK=255.255.255.0
@@ -2341,6 +2378,9 @@ iface bond0 inet6 static
HWADDR=52:54:00:12:34:00
IPV6ADDR=2001:1::100/96
IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
+ NM_CONTROLLED=no
ONBOOT=yes
TYPE=Ethernet
USERCTL=no
@@ -2352,6 +2392,9 @@ iface bond0 inet6 static
HWADDR=52:54:00:12:34:01
IPV6ADDR=2001:1::101/96
IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
+ NM_CONTROLLED=no
ONBOOT=yes
TYPE=Ethernet
USERCTL=no
@@ -3151,6 +3194,61 @@ USERCTL=no
self._compare_files_to_expected(entry[self.expected_name], found)
self._assert_headers(found)
+ def test_stattic6_from_json(self):
+ net_json = {
+ "services": [{"type": "dns", "address": "172.19.0.12"}],
+ "networks": [{
+ "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4",
+ "type": "ipv4", "netmask": "255.255.252.0",
+ "link": "tap1a81968a-79",
+ "routes": [{
+ "netmask": "0.0.0.0",
+ "network": "0.0.0.0",
+ "gateway": "172.19.3.254",
+ }, {
+ "netmask": "0.0.0.0", # A second default gateway
+ "network": "0.0.0.0",
+ "gateway": "172.20.3.254",
+ }],
+ "ip_address": "172.19.1.34", "id": "network0"
+ }, {
+ "network_id": "mgmt",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "link": "interface1",
+ "mode": "link-local",
+ "routes": [],
+ "ip_address": "fe80::c096:67ff:fe5c:6e84",
+ "type": "static6",
+ "id": "network1",
+ "services": [],
+ "accept-ra": "false"
+ }],
+ "links": [
+ {
+ "ethernet_mac_address": "fa:16:3e:ed:9a:59",
+ "mtu": None, "type": "bridge", "id":
+ "tap1a81968a-79",
+ "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f"
+ },
+ ],
+ }
+ macs = {'fa:16:3e:ed:9a:59': 'eth0'}
+ render_dir = self.tmp_dir()
+ network_cfg = openstack.convert_net_json(net_json, known_macs=macs)
+ ns = network_state.parse_net_config_data(network_cfg,
+ skip_broken=False)
+ renderer = self._get_renderer()
+ with self.assertRaises(ValueError):
+ renderer.render_network_state(ns, target=render_dir)
+ self.assertEqual([], os.listdir(render_dir))
+
+ def test_static6_from_yaml(self):
+ entry = NETWORK_CONFIGS['static6']
+ found = self._render_and_read(network_config=yaml.load(
+ entry['yaml']))
+ self._compare_files_to_expected(entry[self.expected_name], found)
+ self._assert_headers(found)
+
def test_dhcpv6_reject_ra_config_v2(self):
entry = NETWORK_CONFIGS['dhcpv6_reject_ra']
found = self._render_and_read(network_config=yaml.load(
@@ -3268,6 +3366,8 @@ USERCTL=no
IPADDR=192.168.42.100
IPV6ADDR=2001:db8::100/32
IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_FORCE_ACCEPT_RA=no
IPV6_DEFAULTGW=2001:db8::1
NETMASK=255.255.255.0
NM_CONTROLLED=no
--
1.8.3.1

View File

@ -0,0 +1,262 @@
From 71989367e7a634fdd2af8ef58473975e0ef60464 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Sat, 21 Aug 2021 13:53:27 +0200
Subject: [PATCH] Fix home permissions modified by ssh module (SC-338) (#984)
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RH-MergeRequest: 29: Fix home permissions modified by ssh module (SC-338) (#984)
RH-Commit: [1/1] c409f2609b1d7e024eba77b55a196a4cafadd1d7 (eesposit/cloud-init)
RH-Bugzilla: 1995840
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
TESTED: By me and QA
BREW: 39178090
Fix home permissions modified by ssh module (SC-338) (#984)
commit 7d3f5d750f6111c2716143364ea33486df67c927
Author: James Falcon <therealfalcon@gmail.com>
Date: Fri Aug 20 17:09:49 2021 -0500
Fix home permissions modified by ssh module (SC-338) (#984)
Fix home permissions modified by ssh module
In #956, we updated the file and directory permissions for keys not in
the user's home directory. We also unintentionally modified the
permissions within the home directory as well. These should not change,
and this commit changes that back.
LP: #1940233
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
cloudinit/ssh_util.py | 35 ++++-
.../modules/test_ssh_keysfile.py | 132 +++++++++++++++---
2 files changed, 146 insertions(+), 21 deletions(-)
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
index b8a3c8f7..9ccadf09 100644
--- a/cloudinit/ssh_util.py
+++ b/cloudinit/ssh_util.py
@@ -321,23 +321,48 @@ def check_create_path(username, filename, strictmodes):
home_folder = os.path.dirname(user_pwent.pw_dir)
for directory in directories:
parent_folder += "/" + directory
- if home_folder.startswith(parent_folder):
+
+ # security check, disallow symlinks in the AuthorizedKeysFile path.
+ if os.path.islink(parent_folder):
+ LOG.debug(
+ "Invalid directory. Symlink exists in path: %s",
+ parent_folder)
+ return False
+
+ if os.path.isfile(parent_folder):
+ LOG.debug(
+ "Invalid directory. File exists in path: %s",
+ parent_folder)
+ return False
+
+ if (home_folder.startswith(parent_folder) or
+ parent_folder == user_pwent.pw_dir):
continue
- if not os.path.isdir(parent_folder):
+ if not os.path.exists(parent_folder):
# directory does not exist, and permission so far are good:
# create the directory, and make it accessible by everyone
# but owned by root, as it might be used by many users.
with util.SeLinuxGuard(parent_folder):
- os.makedirs(parent_folder, mode=0o755, exist_ok=True)
- util.chownbyid(parent_folder, root_pwent.pw_uid,
- root_pwent.pw_gid)
+ mode = 0o755
+ uid = root_pwent.pw_uid
+ gid = root_pwent.pw_gid
+ if parent_folder.startswith(user_pwent.pw_dir):
+ mode = 0o700
+ uid = user_pwent.pw_uid
+ gid = user_pwent.pw_gid
+ os.makedirs(parent_folder, mode=mode, exist_ok=True)
+ util.chownbyid(parent_folder, uid, gid)
permissions = check_permissions(username, parent_folder,
filename, False, strictmodes)
if not permissions:
return False
+ if os.path.islink(filename) or os.path.isdir(filename):
+ LOG.debug("%s is not a file!", filename)
+ return False
+
# check the file
if not os.path.exists(filename):
# if file does not exist: we need to create it, since the
diff --git a/tests/integration_tests/modules/test_ssh_keysfile.py b/tests/integration_tests/modules/test_ssh_keysfile.py
index f82d7649..3159feb9 100644
--- a/tests/integration_tests/modules/test_ssh_keysfile.py
+++ b/tests/integration_tests/modules/test_ssh_keysfile.py
@@ -10,10 +10,10 @@ TEST_USER1_KEYS = get_test_rsa_keypair('test1')
TEST_USER2_KEYS = get_test_rsa_keypair('test2')
TEST_DEFAULT_KEYS = get_test_rsa_keypair('test3')
-USERDATA = """\
+_USERDATA = """\
#cloud-config
bootcmd:
- - sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile /etc/ssh/authorized_keys %h/.ssh/authorized_keys2;' /etc/ssh/sshd_config
+ - {bootcmd}
ssh_authorized_keys:
- {default}
users:
@@ -24,27 +24,17 @@ users:
- name: test_user2
ssh_authorized_keys:
- {user2}
-""".format( # noqa: E501
+""".format(
+ bootcmd='{bootcmd}',
default=TEST_DEFAULT_KEYS.public_key,
user1=TEST_USER1_KEYS.public_key,
user2=TEST_USER2_KEYS.public_key,
)
-@pytest.mark.ubuntu
-@pytest.mark.user_data(USERDATA)
-def test_authorized_keys(client: IntegrationInstance):
- expected_keys = [
- ('test_user1', '/home/test_user1/.ssh/authorized_keys2',
- TEST_USER1_KEYS),
- ('test_user2', '/home/test_user2/.ssh/authorized_keys2',
- TEST_USER2_KEYS),
- ('ubuntu', '/home/ubuntu/.ssh/authorized_keys2',
- TEST_DEFAULT_KEYS),
- ('root', '/root/.ssh/authorized_keys2', TEST_DEFAULT_KEYS),
- ]
-
+def common_verify(client, expected_keys):
for user, filename, keys in expected_keys:
+ # Ensure key is in the key file
contents = client.read_from_file(filename)
if user in ['ubuntu', 'root']:
# Our personal public key gets added by pycloudlib
@@ -83,3 +73,113 @@ def test_authorized_keys(client: IntegrationInstance):
look_for_keys=False,
allow_agent=False,
)
+
+ # Ensure we haven't messed with any /home permissions
+ # See LP: #1940233
+ home_dir = '/home/{}'.format(user)
+ home_perms = '755'
+ if user == 'root':
+ home_dir = '/root'
+ home_perms = '700'
+ assert '{} {}'.format(user, home_perms) == client.execute(
+ 'stat -c "%U %a" {}'.format(home_dir)
+ )
+ if client.execute("test -d {}/.ssh".format(home_dir)).ok:
+ assert '{} 700'.format(user) == client.execute(
+ 'stat -c "%U %a" {}/.ssh'.format(home_dir)
+ )
+ assert '{} 600'.format(user) == client.execute(
+ 'stat -c "%U %a" {}'.format(filename)
+ )
+
+ # Also ensure ssh-keygen works as expected
+ client.execute('mkdir {}/.ssh'.format(home_dir))
+ assert client.execute(
+ "ssh-keygen -b 2048 -t rsa -f {}/.ssh/id_rsa -q -N ''".format(
+ home_dir)
+ ).ok
+ assert client.execute('test -f {}/.ssh/id_rsa'.format(home_dir))
+ assert client.execute('test -f {}/.ssh/id_rsa.pub'.format(home_dir))
+
+ assert 'root 755' == client.execute('stat -c "%U %a" /home')
+
+
+DEFAULT_KEYS_USERDATA = _USERDATA.format(bootcmd='""')
+
+
+@pytest.mark.ubuntu
+@pytest.mark.user_data(DEFAULT_KEYS_USERDATA)
+def test_authorized_keys_default(client: IntegrationInstance):
+ expected_keys = [
+ ('test_user1', '/home/test_user1/.ssh/authorized_keys',
+ TEST_USER1_KEYS),
+ ('test_user2', '/home/test_user2/.ssh/authorized_keys',
+ TEST_USER2_KEYS),
+ ('ubuntu', '/home/ubuntu/.ssh/authorized_keys',
+ TEST_DEFAULT_KEYS),
+ ('root', '/root/.ssh/authorized_keys', TEST_DEFAULT_KEYS),
+ ]
+ common_verify(client, expected_keys)
+
+
+AUTHORIZED_KEYS2_USERDATA = _USERDATA.format(bootcmd=(
+ "sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile "
+ "/etc/ssh/authorized_keys %h/.ssh/authorized_keys2;' "
+ "/etc/ssh/sshd_config"))
+
+
+@pytest.mark.ubuntu
+@pytest.mark.user_data(AUTHORIZED_KEYS2_USERDATA)
+def test_authorized_keys2(client: IntegrationInstance):
+ expected_keys = [
+ ('test_user1', '/home/test_user1/.ssh/authorized_keys2',
+ TEST_USER1_KEYS),
+ ('test_user2', '/home/test_user2/.ssh/authorized_keys2',
+ TEST_USER2_KEYS),
+ ('ubuntu', '/home/ubuntu/.ssh/authorized_keys2',
+ TEST_DEFAULT_KEYS),
+ ('root', '/root/.ssh/authorized_keys2', TEST_DEFAULT_KEYS),
+ ]
+ common_verify(client, expected_keys)
+
+
+NESTED_KEYS_USERDATA = _USERDATA.format(bootcmd=(
+ "sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile "
+ "/etc/ssh/authorized_keys %h/foo/bar/ssh/keys;' "
+ "/etc/ssh/sshd_config"))
+
+
+@pytest.mark.ubuntu
+@pytest.mark.user_data(NESTED_KEYS_USERDATA)
+def test_nested_keys(client: IntegrationInstance):
+ expected_keys = [
+ ('test_user1', '/home/test_user1/foo/bar/ssh/keys',
+ TEST_USER1_KEYS),
+ ('test_user2', '/home/test_user2/foo/bar/ssh/keys',
+ TEST_USER2_KEYS),
+ ('ubuntu', '/home/ubuntu/foo/bar/ssh/keys',
+ TEST_DEFAULT_KEYS),
+ ('root', '/root/foo/bar/ssh/keys', TEST_DEFAULT_KEYS),
+ ]
+ common_verify(client, expected_keys)
+
+
+EXTERNAL_KEYS_USERDATA = _USERDATA.format(bootcmd=(
+ "sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile "
+ "/etc/ssh/authorized_keys /etc/ssh/authorized_keys/%u/keys;' "
+ "/etc/ssh/sshd_config"))
+
+
+@pytest.mark.ubuntu
+@pytest.mark.user_data(EXTERNAL_KEYS_USERDATA)
+def test_external_keys(client: IntegrationInstance):
+ expected_keys = [
+ ('test_user1', '/etc/ssh/authorized_keys/test_user1/keys',
+ TEST_USER1_KEYS),
+ ('test_user2', '/etc/ssh/authorized_keys/test_user2/keys',
+ TEST_USER2_KEYS),
+ ('ubuntu', '/etc/ssh/authorized_keys/ubuntu/keys',
+ TEST_DEFAULT_KEYS),
+ ('root', '/etc/ssh/authorized_keys/root/keys', TEST_DEFAULT_KEYS),
+ ]
+ common_verify(client, expected_keys)
--
2.27.0

View File

@ -1,61 +0,0 @@
From d3889c4645a1319c3d677006164b618ee53f4c8b Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 7 Dec 2020 14:23:22 +0100
Subject: [PATCH 3/4] Fix unit failure of cloud-final.service if NetworkManager
was not present.
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 27: Fix unit failure of cloud-final.service if NetworkManager was not present.
RH-Commit: [1/1] 3c65a2cca140fff48df1ef32919e3cb035506a2b (eterrell/cloud-init)
RH-Bugzilla: 1898943
cloud-final.service would fail if NetworkManager was not installed.
journal -u cloud-final.service would show:
cloud-init[5328]: Cloud-init v. 19.4 finished at ...
echo[5346]: try restart NetworkManager.service
systemctl[5349]: Failed to reload-or-try-restart
NetworkManager.service: Unit not found.
systemd[1]: cloud-final.service: control process exited,
code=exited status=5
systemd[1]: Failed to start Execute cloud user/final scripts.
systemd[1]: Unit cloud-final.service entered failed state.
systemd[1]: cloud-final.service failed.
The change here is to only attempt to restart NetworkManager if it is
present, and its SubState is 'running'.
The multi-line shell in a systemd unit is less than ideal, but I'm not
aware of any other way of conditionally doing this.
Note that both of 'try-reload-or-restart' and 'reload-or-try-restart'
will fail if the service is not present. So this would also affect rhel
8 systems that do not use NetworkManager.
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
rhel/systemd/cloud-final.service | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service
index 05add077..e281c0cf 100644
--- a/rhel/systemd/cloud-final.service
+++ b/rhel/systemd/cloud-final.service
@@ -11,8 +11,11 @@ ExecStart=/usr/bin/cloud-init modules --mode=final
RemainAfterExit=yes
TimeoutSec=0
KillMode=process
-ExecStartPost=/bin/echo "trying to reload or restart NetworkManager.service"
-ExecStartPost=/usr/bin/systemctl try-reload-or-restart NetworkManager.service
+# Restart NetworkManager if it is present and running.
+ExecStartPost=/bin/sh -c 'u=NetworkManager.service; \
+ out=$(systemctl show --property=SubState $u) || exit; \
+ [ "$out" = "SubState=running" ] || exit 0; \
+ systemctl reload-or-try-restart $u'
# Output needs to appear in instance console output
StandardOutput=journal+console
--
2.18.4

View File

@ -1,49 +0,0 @@
From 15852ea6958c18e3830aa9244b36cd0decc93b95 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Thu, 7 Jan 2021 16:51:30 +0100
Subject: [PATCH] Missing IPV6_AUTOCONF=no to render sysconfig dhcp6 stateful
on RHEL (#753)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 29: Missing IPV6_AUTOCONF=no to render sysconfig dhcp6 stateful on RHEL (#753)
RH-Commit: [1/1] 46943f83071d243bcc61f9d987b4fe7d9cf98596 (eterrell/cloud-init)
RH-Bugzilla: 1859695
IPV6_AUTOCONF needs to be set to 'no' on RHEL so NetworkManager can
properly acquire ipv6 address.
rhbz: #1859695
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
cloudinit/net/sysconfig.py | 1 +
tests/unittests/test_net.py | 1 +
2 files changed, 2 insertions(+)
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 94801a93..1793977d 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -397,6 +397,7 @@ class Renderer(renderer.Renderer):
iface_cfg['BOOTPROTO'] = 'dhcp'
iface_cfg['DHCPV6C'] = True
iface_cfg['IPV6INIT'] = True
+ iface_cfg['IPV6_AUTOCONF'] = False
else:
iface_cfg['IPV6INIT'] = True
# Configure network settings using DHCPv6
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index bcd261db..844d5ba8 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1363,6 +1363,7 @@ NETWORK_CONFIGS = {
DEVICE=iface0
DHCPV6C=yes
IPV6INIT=yes
+ IPV6_AUTOCONF=no
IPV6_FORCE_ACCEPT_RA=yes
DEVICE=iface0
NM_CONTROLLED=no
--
2.18.4

View File

@ -1,80 +0,0 @@
From 4dde2a9bed58aba13c730bf4a7314b21038d7a31 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 25 Jan 2021 16:24:29 +0100
Subject: [PATCH 2/2] Revert "ssh_util: handle non-default AuthorizedKeysFile
config (#586)" (#775)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 38: Revert "ssh_util: handle non-default AuthorizedKeysFile config (#586)" (#775)
RH-Commit: [1/1] aec2860c773ad1921f3949dc622543e81860c5bf (eterrell/cloud-init)
RH-Bugzilla: 1919972
commit cdc5b81f33aee0ed3ef1ae239e5cec1906d0178a
Author: Daniel Watkins <oddbloke@ubuntu.com>
Date: Tue Jan 19 12:23:23 2021 -0500
Revert "ssh_util: handle non-default AuthorizedKeysFile config (#586)" (#775)
This reverts commit b0e73814db4027dba0b7dc0282e295b7f653325c.
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
cloudinit/ssh_util.py | 6 +++---
tests/unittests/test_sshutil.py | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
index d5113996..c08042d6 100644
--- a/cloudinit/ssh_util.py
+++ b/cloudinit/ssh_util.py
@@ -262,13 +262,13 @@ def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG):
except (IOError, OSError):
# Give up and use a default key filename
- auth_key_fns.append(default_authorizedkeys_file)
+ auth_key_fns[0] = default_authorizedkeys_file
util.logexc(LOG, "Failed extracting 'AuthorizedKeysFile' in SSH "
"config from %r, using 'AuthorizedKeysFile' file "
"%r instead", DEF_SSHD_CFG, auth_key_fns[0])
- # always store all the keys in the first file configured on sshd_config
- return (auth_key_fns[0], parse_authorized_keys(auth_key_fns))
+ # always store all the keys in the user's private file
+ return (default_authorizedkeys_file, parse_authorized_keys(auth_key_fns))
def setup_user_keys(keys, username, options=None):
diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py
index 88a111e3..fd1d1bac 100644
--- a/tests/unittests/test_sshutil.py
+++ b/tests/unittests/test_sshutil.py
@@ -593,7 +593,7 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
fpw.pw_name, sshd_config)
content = ssh_util.update_authorized_keys(auth_key_entries, [])
- self.assertEqual(authorized_keys, auth_key_fn)
+ self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
self.assertTrue(VALID_CONTENT['rsa'] in content)
self.assertTrue(VALID_CONTENT['dsa'] in content)
@@ -610,7 +610,7 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
sshd_config = self.tmp_path('sshd_config')
util.write_file(
sshd_config,
- "AuthorizedKeysFile %s %s" % (user_keys, authorized_keys)
+ "AuthorizedKeysFile %s %s" % (authorized_keys, user_keys)
)
(auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
@@ -618,7 +618,7 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
)
content = ssh_util.update_authorized_keys(auth_key_entries, [])
- self.assertEqual(user_keys, auth_key_fn)
+ self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
self.assertTrue(VALID_CONTENT['rsa'] in content)
self.assertTrue(VALID_CONTENT['dsa'] in content)
--
2.18.4

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +0,0 @@
From c90d5c11eb99ec25e0fd90585bad9283e60bda7e Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Tue, 26 Jan 2021 10:48:55 +0100
Subject: [PATCH] fix a typo in man page cloud-init.1 (#752)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 39: fix a typo in man page cloud-init.1 (#752)
RH-Commit: [1/1] d2f7efbc63a7928ef175ac0714053dba20aab01a (eterrell/cloud-init)
RH-Bugzilla: 1913127
commit 48b2c5f16bd4ef754fef137ea19894908d4bf1db
Author: Amy Chen <66719270+xiachen-rh@users.noreply.github.com>
Date: Wed Jan 6 22:37:02 2021 +0800
fix a typo in man page cloud-init.1 (#752)
1. fix a typo in cloud-init.1
2. add xiachen-rh as contributor
Conflict: We don't really use tools/.github-cla-signers, but had to fix
a tiny conflict of already included names on the file.
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
doc/man/cloud-init.1 | 2 +-
tools/.github-cla-signers | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/doc/man/cloud-init.1 b/doc/man/cloud-init.1
index 9b52dc8d..3fde4148 100644
--- a/doc/man/cloud-init.1
+++ b/doc/man/cloud-init.1
@@ -10,7 +10,7 @@ cloud-init \- Cloud instance initialization
Cloud-init provides a mechanism for cloud instance initialization.
This is done by identifying the cloud platform that is in use, reading
provided cloud metadata and optional vendor and user
-data, and then intializing the instance as requested.
+data, and then initializing the instance as requested.
Generally, this command is not normally meant to be run directly by
the user. However, some subcommands may useful for development or
diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers
index 802a35bd..e5d2b95c 100644
--- a/tools/.github-cla-signers
+++ b/tools/.github-cla-signers
@@ -21,3 +21,4 @@ sshedi
TheRealFalcon
tomponline
tsanghan
+xiachen-rh
--
2.18.4

View File

@ -1,247 +0,0 @@
From 51a90ecbdf1f3900183d8ec641eeb4571decf6dc Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Wed, 4 Nov 2020 12:37:54 +0100
Subject: [PATCH] network: Fix type and respect name when rendering vlan in
sysconfig. (#541)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 19: network: Fix type and respect name when rendering vlan in sysconfig. (#541)
RH-Commit: [1/1] 75bea46017397082c5763125a5f35806c2f840e9 (eterrell/cloud-init)
RH-Bugzilla: 1881462
commit 8439b191ec2f336d544cab86dba2860f969cd5b8
Author: Eduardo Otubo <otubo@redhat.com>
Date: Tue Sep 15 18:00:00 2020 +0200
network: Fix type and respect name when rendering vlan in sysconfig. (#541)
Prior to this change, vlans were rendered in sysconfig with
'TYPE=Ethernet', and incorrectly rendered the PHYSDEV based on
the name of the vlan device rather than the 'link' provided
in the network config.
The change here fixes:
* rendering of TYPE=Ethernet for a vlan
* adds a warning if the configured device name is not supported
per the RHEL 7 docs "11.5. Naming Scheme for VLAN Interfaces"
LP: #1788915
LP: #1826608
RHBZ: #1861871
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
cloudinit/net/sysconfig.py | 32 +++++++++-
tests/unittests/test_distros/test_netconfig.py | 81 ++++++++++++++++++++++++++
tests/unittests/test_net.py | 4 --
3 files changed, 112 insertions(+), 5 deletions(-)
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index c078898..078636a 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -99,6 +99,10 @@ class ConfigMap(object):
def __len__(self):
return len(self._conf)
+ def skip_key_value(self, key, val):
+ """Skip the pair key, value if it matches a certain rule."""
+ return False
+
def to_string(self):
buf = io.StringIO()
buf.write(_make_header())
@@ -106,6 +110,8 @@ class ConfigMap(object):
buf.write("\n")
for key in sorted(self._conf.keys()):
value = self._conf[key]
+ if self.skip_key_value(key, value):
+ continue
if isinstance(value, bool):
value = self._bool_map[value]
if not isinstance(value, str):
@@ -214,6 +220,7 @@ class NetInterface(ConfigMap):
'bond': 'Bond',
'bridge': 'Bridge',
'infiniband': 'InfiniBand',
+ 'vlan': 'Vlan',
}
def __init__(self, iface_name, base_sysconf_dir, templates,
@@ -267,6 +274,11 @@ class NetInterface(ConfigMap):
c.routes = self.routes.copy()
return c
+ def skip_key_value(self, key, val):
+ if key == 'TYPE' and val == 'Vlan':
+ return True
+ return False
+
class Renderer(renderer.Renderer):
"""Renders network information in a /etc/sysconfig format."""
@@ -701,7 +713,16 @@ class Renderer(renderer.Renderer):
iface_cfg['ETHERDEVICE'] = iface_name[:iface_name.rfind('.')]
else:
iface_cfg['VLAN'] = True
- iface_cfg['PHYSDEV'] = iface_name[:iface_name.rfind('.')]
+ iface_cfg.kind = 'vlan'
+
+ rdev = iface['vlan-raw-device']
+ supported = _supported_vlan_names(rdev, iface['vlan_id'])
+ if iface_name not in supported:
+ LOG.info(
+ "Name '%s' for vlan '%s' is not officially supported"
+ "by RHEL. Supported: %s",
+ iface_name, rdev, ' '.join(supported))
+ iface_cfg['PHYSDEV'] = rdev
iface_subnets = iface.get("subnets", [])
route_cfg = iface_cfg.routes
@@ -909,6 +930,15 @@ class Renderer(renderer.Renderer):
"\n".join(netcfg) + "\n", file_mode)
+def _supported_vlan_names(rdev, vid):
+ """Return list of supported names for vlan devices per RHEL doc
+ 11.5. Naming Scheme for VLAN Interfaces."""
+ return [
+ v.format(rdev=rdev, vid=int(vid))
+ for v in ("{rdev}{vid:04}", "{rdev}{vid}",
+ "{rdev}.{vid:04}", "{rdev}.{vid}")]
+
+
def available(target=None):
sysconfig = available_sysconfig(target=target)
nm = available_nm(target=target)
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index f9fc3a1..a1df066 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -541,6 +541,87 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
V1_NET_CFG_IPV6,
expected_cfgs=expected_cfgs.copy())
+ def test_vlan_render_unsupported(self):
+ """Render officially unsupported vlan names."""
+ cfg = {
+ 'version': 2,
+ 'ethernets': {
+ 'eth0': {'addresses': ["192.10.1.2/24"],
+ 'match': {'macaddress': "00:16:3e:60:7c:df"}}},
+ 'vlans': {
+ 'infra0': {'addresses': ["10.0.1.2/16"],
+ 'id': 1001, 'link': 'eth0'}},
+ }
+ expected_cfgs = {
+ self.ifcfg_path('eth0'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0
+ HWADDR=00:16:3e:60:7c:df
+ IPADDR=192.10.1.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ self.ifcfg_path('infra0'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=infra0
+ IPADDR=10.0.1.2
+ NETMASK=255.255.0.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=eth0
+ USERCTL=no
+ VLAN=yes
+ """),
+ self.control_path(): dedent("""\
+ NETWORKING=yes
+ """),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config, cfg,
+ expected_cfgs=expected_cfgs)
+
+ def test_vlan_render(self):
+ cfg = {
+ 'version': 2,
+ 'ethernets': {
+ 'eth0': {'addresses': ["192.10.1.2/24"]}},
+ 'vlans': {
+ 'eth0.1001': {'addresses': ["10.0.1.2/16"],
+ 'id': 1001, 'link': 'eth0'}},
+ }
+ expected_cfgs = {
+ self.ifcfg_path('eth0'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0
+ IPADDR=192.10.1.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ self.ifcfg_path('eth0.1001'): dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0.1001
+ IPADDR=10.0.1.2
+ NETMASK=255.255.0.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=eth0
+ USERCTL=no
+ VLAN=yes
+ """),
+ self.control_path(): dedent("""\
+ NETWORKING=yes
+ """),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config, cfg,
+ expected_cfgs=expected_cfgs)
+
class TestNetCfgDistroOpensuse(TestNetCfgDistroBase):
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index d7a7a65..c033745 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1656,7 +1656,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
DHCLIENT_SET_DEFAULT_ROUTE=no
ONBOOT=yes
PHYSDEV=bond0
- TYPE=Ethernet
USERCTL=no
VLAN=yes"""),
'ifcfg-br0': textwrap.dedent("""\
@@ -1699,7 +1698,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
NETMASK1=255.255.255.0
ONBOOT=yes
PHYSDEV=eth0
- TYPE=Ethernet
USERCTL=no
VLAN=yes"""),
'ifcfg-eth1': textwrap.dedent("""\
@@ -2302,7 +2300,6 @@ iface bond0 inet6 static
NETMASK1=255.255.255.0
ONBOOT=yes
PHYSDEV=en0
- TYPE=Ethernet
USERCTL=no
VLAN=yes"""),
},
@@ -3409,7 +3406,6 @@ USERCTL=no
NM_CONTROLLED=no
ONBOOT=yes
PHYSDEV=eno1
- TYPE=Ethernet
USERCTL=no
VLAN=yes
""")
--
1.8.3.1

View File

@ -0,0 +1,65 @@
From abf1adeae8211f5acd87dc63b03b2ed995047efd Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Thu, 20 May 2021 08:53:55 +0200
Subject: [PATCH 1/2] rhel/cloud.cfg: remove ssh_genkeytypes in settings.py and
set in cloud.cfg
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RH-MergeRequest: 10: rhel/cloud.cfg: remove ssh_genkeytypes in settings.py and set in cloud.cfg
RH-Commit: [1/1] 6da989423b9b6e017afbac2f1af3649b0487310f
RH-Bugzilla: 1957532
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
RH-Acked-by: Cathy Avery <cavery@redhat.com>
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
Currently genkeytypes in cloud.cfg is set to None, so together with
ssh_deletekeys=1 cloudinit on first boot it will just delete the existing
keys and not generate new ones.
Just removing that property in cloud.cfg is not enough, because
settings.py provides another empty default value that will be used
instead, resulting to no key generated even when the property is not defined.
Removing genkeytypes also in settings.py will default to GENERATE_KEY_NAMES,
but since we want only 'rsa', 'ecdsa' and 'ed25519', add back genkeytypes in
cloud.cfg with the above defaults.
Also remove ssh_deletekeys in settings.py as we always need
to 1 (and it also defaults to 1).
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
cloudinit/settings.py | 2 --
rhel/cloud.cfg | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/cloudinit/settings.py b/cloudinit/settings.py
index 43a1490c..2acf2615 100644
--- a/cloudinit/settings.py
+++ b/cloudinit/settings.py
@@ -49,8 +49,6 @@ CFG_BUILTIN = {
'def_log_file_mode': 0o600,
'log_cfgs': [],
'mount_default_fields': [None, None, 'auto', 'defaults,nofail', '0', '2'],
- 'ssh_deletekeys': False,
- 'ssh_genkeytypes': [],
'syslog_fix_perms': [],
'system_info': {
'paths': {
diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg
index 9ecba215..cbee197a 100644
--- a/rhel/cloud.cfg
+++ b/rhel/cloud.cfg
@@ -7,7 +7,7 @@ ssh_pwauth: 0
mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service', '0', '2']
resize_rootfs_tmp: /dev
ssh_deletekeys: 1
-ssh_genkeytypes: ~
+ssh_genkeytypes: ['rsa', 'ecdsa', 'ed25519']
syslog_fix_perms: ~
disable_vmware_customization: false
--
2.27.0

View File

@ -0,0 +1,653 @@
From aeab67600eb2d5e483812620b56ce5fb031a57d6 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Mon, 12 Jul 2021 21:47:37 +0200
Subject: [PATCH] ssh-util: allow cloudinit to merge all ssh keys into a custom
user file, defined in AuthorizedKeysFile (#937)
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RH-MergeRequest: 25: ssh-util: allow cloudinit to merge all ssh keys into a custom user file, defined in AuthorizedKeysFile (#937)
RH-Commit: [1/1] 27bbe94f3b9dd8734865766bd30b06cff83383ab (eesposit/cloud-init)
RH-Bugzilla: 1862967
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
TESTED: By me and QA
BREW: 38030830
Conflicts: upstream patch modifies tests/integration_tests/util.py, that is
not present in RHEL.
commit 9b52405c6f0de5e00d5ee9c1d13540425d8f6bf5
Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Mon Jul 12 20:21:02 2021 +0200
ssh-util: allow cloudinit to merge all ssh keys into a custom user file, defined in AuthorizedKeysFile (#937)
This patch aims to fix LP1911680, by analyzing the files provided
in sshd_config and merge all keys into an user-specific file. Also
introduces additional tests to cover this specific case.
The file is picked by analyzing the path given in AuthorizedKeysFile.
If it points inside the current user folder (path is /home/user/*), it
means it is an user-specific file, so we can copy all user-keys there.
If it contains a %u or %h, it means that there will be a specific
authorized_keys file for each user, so we can copy all user-keys there.
If no path points to an user-specific file, for example when only
/etc/ssh/authorized_keys is given, default to ~/.ssh/authorized_keys.
Note that if there are more than a single user-specific file, the last
one will be picked.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Co-authored-by: James Falcon <therealfalcon@gmail.com>
LP: #1911680
RHBZ:1862967
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
cloudinit/ssh_util.py | 22 +-
.../assets/keys/id_rsa.test1 | 38 +++
.../assets/keys/id_rsa.test1.pub | 1 +
.../assets/keys/id_rsa.test2 | 38 +++
.../assets/keys/id_rsa.test2.pub | 1 +
.../assets/keys/id_rsa.test3 | 38 +++
.../assets/keys/id_rsa.test3.pub | 1 +
.../modules/test_ssh_keysfile.py | 85 ++++++
tests/unittests/test_sshutil.py | 246 +++++++++++++++++-
9 files changed, 456 insertions(+), 14 deletions(-)
create mode 100644 tests/integration_tests/assets/keys/id_rsa.test1
create mode 100644 tests/integration_tests/assets/keys/id_rsa.test1.pub
create mode 100644 tests/integration_tests/assets/keys/id_rsa.test2
create mode 100644 tests/integration_tests/assets/keys/id_rsa.test2.pub
create mode 100644 tests/integration_tests/assets/keys/id_rsa.test3
create mode 100644 tests/integration_tests/assets/keys/id_rsa.test3.pub
create mode 100644 tests/integration_tests/modules/test_ssh_keysfile.py
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
index c08042d6..89057262 100644
--- a/cloudinit/ssh_util.py
+++ b/cloudinit/ssh_util.py
@@ -252,13 +252,15 @@ def render_authorizedkeysfile_paths(value, homedir, username):
def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG):
(ssh_dir, pw_ent) = users_ssh_info(username)
default_authorizedkeys_file = os.path.join(ssh_dir, 'authorized_keys')
+ user_authorizedkeys_file = default_authorizedkeys_file
auth_key_fns = []
with util.SeLinuxGuard(ssh_dir, recursive=True):
try:
ssh_cfg = parse_ssh_config_map(sshd_cfg_file)
+ key_paths = ssh_cfg.get("authorizedkeysfile",
+ "%h/.ssh/authorized_keys")
auth_key_fns = render_authorizedkeysfile_paths(
- ssh_cfg.get("authorizedkeysfile", "%h/.ssh/authorized_keys"),
- pw_ent.pw_dir, username)
+ key_paths, pw_ent.pw_dir, username)
except (IOError, OSError):
# Give up and use a default key filename
@@ -267,8 +269,22 @@ def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG):
"config from %r, using 'AuthorizedKeysFile' file "
"%r instead", DEF_SSHD_CFG, auth_key_fns[0])
+ # check if one of the keys is the user's one
+ for key_path, auth_key_fn in zip(key_paths.split(), auth_key_fns):
+ if any([
+ '%u' in key_path,
+ '%h' in key_path,
+ auth_key_fn.startswith('{}/'.format(pw_ent.pw_dir))
+ ]):
+ user_authorizedkeys_file = auth_key_fn
+
+ if user_authorizedkeys_file != default_authorizedkeys_file:
+ LOG.debug(
+ "AuthorizedKeysFile has an user-specific authorized_keys, "
+ "using %s", user_authorizedkeys_file)
+
# always store all the keys in the user's private file
- return (default_authorizedkeys_file, parse_authorized_keys(auth_key_fns))
+ return (user_authorizedkeys_file, parse_authorized_keys(auth_key_fns))
def setup_user_keys(keys, username, options=None):
diff --git a/tests/integration_tests/assets/keys/id_rsa.test1 b/tests/integration_tests/assets/keys/id_rsa.test1
new file mode 100644
index 00000000..bd4c822e
--- /dev/null
+++ b/tests/integration_tests/assets/keys/id_rsa.test1
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+CHbTWbsFrHR1qFAAAACmphbWVzQG5ld3Q=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/integration_tests/assets/keys/id_rsa.test1.pub b/tests/integration_tests/assets/keys/id_rsa.test1.pub
new file mode 100644
index 00000000..3d2e26e1
--- /dev/null
+++ b/tests/integration_tests/assets/keys/id_rsa.test1.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC1GUb3ponbdRG8CA79sGy4uX6Wq5zfnRpLBX3z+Ly+W86fkZVwfJ63+tdCDiVnizSjAbeRbJAVjye9OT4b5Q1Tx83RqcDMEOL7gzOx6OQX2xyNMwWZdWsikgKpXdthA2M+/qv65tB2QPTV27d4H23n/OXziC2yGnSEF2h/LPy7X8Dbmzt9VTrsbIjs90q9pL7K8lIidmdvZ71VDN8kL6oCc3uBRlq0EqN2BS0l6JRR4NpWdpLXtvRqhP2IE7SwhxC05MGViUKMW3VMAwtnrgePdJ8ZkHtiPRn4vMUHYhZfXPOyMjrxiRg8o5Iv81iFBT17nmQlDtaeeA0dj/af8Dt00pIUOaAzlz0haB5nt+Omx8hgCgn8IDEUg0P48Qd3WAJLrGgSFpRXKHlYf9eTAq/WfHgCJnYEUIT7vmTJJEtlQn7nLSvcNTmS0xd5RHNUg/um7RHwMK6X4ZT4z/5MfJa0AQ6B5o0np9imjaKkOFuZNi3eSTaUwhqlHEYEWz7fux0= test1@host
diff --git a/tests/integration_tests/assets/keys/id_rsa.test2 b/tests/integration_tests/assets/keys/id_rsa.test2
new file mode 100644
index 00000000..5854d901
--- /dev/null
+++ b/tests/integration_tests/assets/keys/id_rsa.test2
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+tZRSm2GEgnDGAvAAAACmphbWVzQG5ld3Q=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/integration_tests/assets/keys/id_rsa.test2.pub b/tests/integration_tests/assets/keys/id_rsa.test2.pub
new file mode 100644
index 00000000..f3831a57
--- /dev/null
+++ b/tests/integration_tests/assets/keys/id_rsa.test2.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8rnQPY9Y5ziKTIdVElLq0OGrOMvlwqKK8gPinVfwFgJXDzdcAQY4uci1TJVcc0AOWHp+lWrU1joDYlW3KCg8XpkXHymHshYyaeEN2fEsvIaxuF3UzW2JckP9H7dacYdEng8qtBq8wuCodGuJ5XdBVV+MVJ6jqNf/hOu4/pma8hMxlYmtdoamHEn+k/KQR2Q6LwCYpT0U2+KiPI9Lac22P0H/rfkh2Ba+t3tV/lhyzD0xHnktZKJ3BrznpEfrZiXoukQZMebWUmIyTW7zwk9Kq+iGFpSQsqQWlxDBwHSbvpbUo7KWUmyZfxs1euVmwj5aKJgjteXm9CbbXsMS415q+08C0MzG7weZODZQnnk1rF6Ft97aXHaTmRgYbDeLU7U5U3alnb84HvUu5xh3/mrE9rPTfCzBwY4lgY+Q1zjS90RL9Jxzti3weydm7OqTI6DOfQeJQLOhhRgthOkt/7IabDFLIARjTnpo5+QKxugoc4qI6aUnE1plkVCRfV696ngM= test2@host
diff --git a/tests/integration_tests/assets/keys/id_rsa.test3 b/tests/integration_tests/assets/keys/id_rsa.test3
new file mode 100644
index 00000000..2596c762
--- /dev/null
+++ b/tests/integration_tests/assets/keys/id_rsa.test3
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+eauhOY52PS/BcAAAAKamFtZXNAbmV3dAE=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/integration_tests/assets/keys/id_rsa.test3.pub b/tests/integration_tests/assets/keys/id_rsa.test3.pub
new file mode 100644
index 00000000..057db632
--- /dev/null
+++ b/tests/integration_tests/assets/keys/id_rsa.test3.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCk8bgx2RhAoPnv+qt4WuH0ZELbbLrqpk5ZlZGMLzzuutu8HM7r2FXImlMheV4kS+laIdhyMxHkbo00Wyc21nh/EOqZJzi9wWPncmwT9c2osqrqmxdp0Jvm+Q2kZvED5nlsmXMn3r0+AkggRuBzzqx6LiSBHYy49aq1ltjkaSk0JOpa4th4Ur7XigWu4DafJYf0w4hEMcr93n3umwfL4tMy3KY7tk+E7nvVpsrDqO+/CJJ4PFUT2RXsoBAi4z39LeB+2+BBCuifcYvfimCYhpt1IZ1t8eoYOiivImGVuq4lkQsE3bfoqBE1MICNW9TOONCvV5E/fGywbBU7oQIEYJAuX9piiACC3CfCP2Jeq1+IsrcTCWCOq5Tj4lDQJW0uEbeAhkH6qgxOFyjSmYdOuO19ALCvO2wEEW6dJrb8Ky25XWZDk7lmtnoAKj3I9EE2Sezr4KIp20y0K3OLeTZEPP2OliY9xqTTcuvsiR0LVOnr6MD7//I8dI7WjgKpbz0hDM0= test3@host
diff --git a/tests/integration_tests/modules/test_ssh_keysfile.py b/tests/integration_tests/modules/test_ssh_keysfile.py
new file mode 100644
index 00000000..f82d7649
--- /dev/null
+++ b/tests/integration_tests/modules/test_ssh_keysfile.py
@@ -0,0 +1,85 @@
+import paramiko
+import pytest
+from io import StringIO
+from paramiko.ssh_exception import SSHException
+
+from tests.integration_tests.instances import IntegrationInstance
+from tests.integration_tests.util import get_test_rsa_keypair
+
+TEST_USER1_KEYS = get_test_rsa_keypair('test1')
+TEST_USER2_KEYS = get_test_rsa_keypair('test2')
+TEST_DEFAULT_KEYS = get_test_rsa_keypair('test3')
+
+USERDATA = """\
+#cloud-config
+bootcmd:
+ - sed -i 's;#AuthorizedKeysFile.*;AuthorizedKeysFile /etc/ssh/authorized_keys %h/.ssh/authorized_keys2;' /etc/ssh/sshd_config
+ssh_authorized_keys:
+ - {default}
+users:
+- default
+- name: test_user1
+ ssh_authorized_keys:
+ - {user1}
+- name: test_user2
+ ssh_authorized_keys:
+ - {user2}
+""".format( # noqa: E501
+ default=TEST_DEFAULT_KEYS.public_key,
+ user1=TEST_USER1_KEYS.public_key,
+ user2=TEST_USER2_KEYS.public_key,
+)
+
+
+@pytest.mark.ubuntu
+@pytest.mark.user_data(USERDATA)
+def test_authorized_keys(client: IntegrationInstance):
+ expected_keys = [
+ ('test_user1', '/home/test_user1/.ssh/authorized_keys2',
+ TEST_USER1_KEYS),
+ ('test_user2', '/home/test_user2/.ssh/authorized_keys2',
+ TEST_USER2_KEYS),
+ ('ubuntu', '/home/ubuntu/.ssh/authorized_keys2',
+ TEST_DEFAULT_KEYS),
+ ('root', '/root/.ssh/authorized_keys2', TEST_DEFAULT_KEYS),
+ ]
+
+ for user, filename, keys in expected_keys:
+ contents = client.read_from_file(filename)
+ if user in ['ubuntu', 'root']:
+ # Our personal public key gets added by pycloudlib
+ lines = contents.split('\n')
+ assert len(lines) == 2
+ assert keys.public_key.strip() in contents
+ else:
+ assert contents.strip() == keys.public_key.strip()
+
+ # Ensure we can actually connect
+ ssh = paramiko.SSHClient()
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ paramiko_key = paramiko.RSAKey.from_private_key(StringIO(
+ keys.private_key))
+
+ # Will fail with AuthenticationException if
+ # we cannot connect
+ ssh.connect(
+ client.instance.ip,
+ username=user,
+ pkey=paramiko_key,
+ look_for_keys=False,
+ allow_agent=False,
+ )
+
+ # Ensure other uses can't connect using our key
+ other_users = [u[0] for u in expected_keys if u[2] != keys]
+ for other_user in other_users:
+ with pytest.raises(SSHException):
+ print('trying to connect as {} with key from {}'.format(
+ other_user, user))
+ ssh.connect(
+ client.instance.ip,
+ username=other_user,
+ pkey=paramiko_key,
+ look_for_keys=False,
+ allow_agent=False,
+ )
diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py
index fd1d1bac..bcb8044f 100644
--- a/tests/unittests/test_sshutil.py
+++ b/tests/unittests/test_sshutil.py
@@ -570,20 +570,33 @@ class TestBasicAuthorizedKeyParse(test_helpers.CiTestCase):
ssh_util.render_authorizedkeysfile_paths(
"%h/.keys", "/homedirs/bobby", "bobby"))
+ def test_all(self):
+ self.assertEqual(
+ ["/homedirs/bobby/.keys", "/homedirs/bobby/.secret/keys",
+ "/keys/path1", "/opt/bobby/keys"],
+ ssh_util.render_authorizedkeysfile_paths(
+ "%h/.keys .secret/keys /keys/path1 /opt/%u/keys",
+ "/homedirs/bobby", "bobby"))
+
class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
@patch("cloudinit.ssh_util.pwd.getpwnam")
def test_multiple_authorizedkeys_file_order1(self, m_getpwnam):
- fpw = FakePwEnt(pw_name='bobby', pw_dir='/home2/bobby')
+ fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
m_getpwnam.return_value = fpw
- authorized_keys = self.tmp_path('authorized_keys')
+ user_ssh_folder = "%s/.ssh" % fpw.pw_dir
+
+ # /tmp/home2/bobby/.ssh/authorized_keys = rsa
+ authorized_keys = self.tmp_path('authorized_keys', dir=user_ssh_folder)
util.write_file(authorized_keys, VALID_CONTENT['rsa'])
- user_keys = self.tmp_path('user_keys')
+ # /tmp/home2/bobby/.ssh/user_keys = dsa
+ user_keys = self.tmp_path('user_keys', dir=user_ssh_folder)
util.write_file(user_keys, VALID_CONTENT['dsa'])
- sshd_config = self.tmp_path('sshd_config')
+ # /tmp/sshd_config
+ sshd_config = self.tmp_path('sshd_config', dir="/tmp")
util.write_file(
sshd_config,
"AuthorizedKeysFile %s %s" % (authorized_keys, user_keys)
@@ -593,33 +606,244 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
fpw.pw_name, sshd_config)
content = ssh_util.update_authorized_keys(auth_key_entries, [])
- self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
+ self.assertEqual(user_keys, auth_key_fn)
self.assertTrue(VALID_CONTENT['rsa'] in content)
self.assertTrue(VALID_CONTENT['dsa'] in content)
@patch("cloudinit.ssh_util.pwd.getpwnam")
def test_multiple_authorizedkeys_file_order2(self, m_getpwnam):
- fpw = FakePwEnt(pw_name='suzie', pw_dir='/home/suzie')
+ fpw = FakePwEnt(pw_name='suzie', pw_dir='/tmp/home/suzie')
m_getpwnam.return_value = fpw
- authorized_keys = self.tmp_path('authorized_keys')
+ user_ssh_folder = "%s/.ssh" % fpw.pw_dir
+
+ # /tmp/home/suzie/.ssh/authorized_keys = rsa
+ authorized_keys = self.tmp_path('authorized_keys', dir=user_ssh_folder)
util.write_file(authorized_keys, VALID_CONTENT['rsa'])
- user_keys = self.tmp_path('user_keys')
+ # /tmp/home/suzie/.ssh/user_keys = dsa
+ user_keys = self.tmp_path('user_keys', dir=user_ssh_folder)
util.write_file(user_keys, VALID_CONTENT['dsa'])
- sshd_config = self.tmp_path('sshd_config')
+ # /tmp/sshd_config
+ sshd_config = self.tmp_path('sshd_config', dir="/tmp")
util.write_file(
sshd_config,
- "AuthorizedKeysFile %s %s" % (authorized_keys, user_keys)
+ "AuthorizedKeysFile %s %s" % (user_keys, authorized_keys)
)
(auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
- fpw.pw_name, sshd_config
+ fpw.pw_name, sshd_config)
+ content = ssh_util.update_authorized_keys(auth_key_entries, [])
+
+ self.assertEqual(authorized_keys, auth_key_fn)
+ self.assertTrue(VALID_CONTENT['rsa'] in content)
+ self.assertTrue(VALID_CONTENT['dsa'] in content)
+
+ @patch("cloudinit.ssh_util.pwd.getpwnam")
+ def test_multiple_authorizedkeys_file_local_global(self, m_getpwnam):
+ fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
+ m_getpwnam.return_value = fpw
+ user_ssh_folder = "%s/.ssh" % fpw.pw_dir
+
+ # /tmp/home2/bobby/.ssh/authorized_keys = rsa
+ authorized_keys = self.tmp_path('authorized_keys', dir=user_ssh_folder)
+ util.write_file(authorized_keys, VALID_CONTENT['rsa'])
+
+ # /tmp/home2/bobby/.ssh/user_keys = dsa
+ user_keys = self.tmp_path('user_keys', dir=user_ssh_folder)
+ util.write_file(user_keys, VALID_CONTENT['dsa'])
+
+ # /tmp/etc/ssh/authorized_keys = ecdsa
+ authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys',
+ dir="/tmp")
+ util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
+
+ # /tmp/sshd_config
+ sshd_config = self.tmp_path('sshd_config', dir="/tmp")
+ util.write_file(
+ sshd_config,
+ "AuthorizedKeysFile %s %s %s" % (authorized_keys_global,
+ user_keys, authorized_keys)
+ )
+
+ (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
+ fpw.pw_name, sshd_config)
+ content = ssh_util.update_authorized_keys(auth_key_entries, [])
+
+ self.assertEqual(authorized_keys, auth_key_fn)
+ self.assertTrue(VALID_CONTENT['rsa'] in content)
+ self.assertTrue(VALID_CONTENT['ecdsa'] in content)
+ self.assertTrue(VALID_CONTENT['dsa'] in content)
+
+ @patch("cloudinit.ssh_util.pwd.getpwnam")
+ def test_multiple_authorizedkeys_file_local_global2(self, m_getpwnam):
+ fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
+ m_getpwnam.return_value = fpw
+ user_ssh_folder = "%s/.ssh" % fpw.pw_dir
+
+ # /tmp/home2/bobby/.ssh/authorized_keys2 = rsa
+ authorized_keys = self.tmp_path('authorized_keys2',
+ dir=user_ssh_folder)
+ util.write_file(authorized_keys, VALID_CONTENT['rsa'])
+
+ # /tmp/home2/bobby/.ssh/user_keys3 = dsa
+ user_keys = self.tmp_path('user_keys3', dir=user_ssh_folder)
+ util.write_file(user_keys, VALID_CONTENT['dsa'])
+
+ # /tmp/etc/ssh/authorized_keys = ecdsa
+ authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys',
+ dir="/tmp")
+ util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
+
+ # /tmp/sshd_config
+ sshd_config = self.tmp_path('sshd_config', dir="/tmp")
+ util.write_file(
+ sshd_config,
+ "AuthorizedKeysFile %s %s %s" % (authorized_keys_global,
+ authorized_keys, user_keys)
+ )
+
+ (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
+ fpw.pw_name, sshd_config)
+ content = ssh_util.update_authorized_keys(auth_key_entries, [])
+
+ self.assertEqual(user_keys, auth_key_fn)
+ self.assertTrue(VALID_CONTENT['rsa'] in content)
+ self.assertTrue(VALID_CONTENT['ecdsa'] in content)
+ self.assertTrue(VALID_CONTENT['dsa'] in content)
+
+ @patch("cloudinit.ssh_util.pwd.getpwnam")
+ def test_multiple_authorizedkeys_file_global(self, m_getpwnam):
+ fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
+ m_getpwnam.return_value = fpw
+
+ # /tmp/etc/ssh/authorized_keys = rsa
+ authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys',
+ dir="/tmp")
+ util.write_file(authorized_keys_global, VALID_CONTENT['rsa'])
+
+ # /tmp/sshd_config
+ sshd_config = self.tmp_path('sshd_config')
+ util.write_file(
+ sshd_config,
+ "AuthorizedKeysFile %s" % (authorized_keys_global)
)
+
+ (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
+ fpw.pw_name, sshd_config)
content = ssh_util.update_authorized_keys(auth_key_entries, [])
self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
self.assertTrue(VALID_CONTENT['rsa'] in content)
+
+ @patch("cloudinit.ssh_util.pwd.getpwnam")
+ def test_multiple_authorizedkeys_file_multiuser(self, m_getpwnam):
+ fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home2/bobby')
+ m_getpwnam.return_value = fpw
+ user_ssh_folder = "%s/.ssh" % fpw.pw_dir
+ # /tmp/home2/bobby/.ssh/authorized_keys2 = rsa
+ authorized_keys = self.tmp_path('authorized_keys2',
+ dir=user_ssh_folder)
+ util.write_file(authorized_keys, VALID_CONTENT['rsa'])
+ # /tmp/home2/bobby/.ssh/user_keys3 = dsa
+ user_keys = self.tmp_path('user_keys3', dir=user_ssh_folder)
+ util.write_file(user_keys, VALID_CONTENT['dsa'])
+
+ fpw2 = FakePwEnt(pw_name='suzie', pw_dir='/tmp/home/suzie')
+ user_ssh_folder = "%s/.ssh" % fpw2.pw_dir
+ # /tmp/home/suzie/.ssh/authorized_keys2 = ssh-xmss@openssh.com
+ authorized_keys2 = self.tmp_path('authorized_keys2',
+ dir=user_ssh_folder)
+ util.write_file(authorized_keys2,
+ VALID_CONTENT['ssh-xmss@openssh.com'])
+
+ # /tmp/etc/ssh/authorized_keys = ecdsa
+ authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys2',
+ dir="/tmp")
+ util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
+
+ # /tmp/sshd_config
+ sshd_config = self.tmp_path('sshd_config', dir="/tmp")
+ util.write_file(
+ sshd_config,
+ "AuthorizedKeysFile %s %%h/.ssh/authorized_keys2 %s" %
+ (authorized_keys_global, user_keys)
+ )
+
+ # process first user
+ (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
+ fpw.pw_name, sshd_config)
+ content = ssh_util.update_authorized_keys(auth_key_entries, [])
+
+ self.assertEqual(user_keys, auth_key_fn)
+ self.assertTrue(VALID_CONTENT['rsa'] in content)
+ self.assertTrue(VALID_CONTENT['ecdsa'] in content)
+ self.assertTrue(VALID_CONTENT['dsa'] in content)
+ self.assertFalse(VALID_CONTENT['ssh-xmss@openssh.com'] in content)
+
+ m_getpwnam.return_value = fpw2
+ # process second user
+ (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
+ fpw2.pw_name, sshd_config)
+ content = ssh_util.update_authorized_keys(auth_key_entries, [])
+
+ self.assertEqual(authorized_keys2, auth_key_fn)
+ self.assertTrue(VALID_CONTENT['ssh-xmss@openssh.com'] in content)
+ self.assertTrue(VALID_CONTENT['ecdsa'] in content)
+ self.assertTrue(VALID_CONTENT['dsa'] in content)
+ self.assertFalse(VALID_CONTENT['rsa'] in content)
+
+ @patch("cloudinit.ssh_util.pwd.getpwnam")
+ def test_multiple_authorizedkeys_file_multiuser2(self, m_getpwnam):
+ fpw = FakePwEnt(pw_name='bobby', pw_dir='/tmp/home/bobby')
+ m_getpwnam.return_value = fpw
+ user_ssh_folder = "%s/.ssh" % fpw.pw_dir
+ # /tmp/home/bobby/.ssh/authorized_keys2 = rsa
+ authorized_keys = self.tmp_path('authorized_keys2',
+ dir=user_ssh_folder)
+ util.write_file(authorized_keys, VALID_CONTENT['rsa'])
+ # /tmp/home/bobby/.ssh/user_keys3 = dsa
+ user_keys = self.tmp_path('user_keys3', dir=user_ssh_folder)
+ util.write_file(user_keys, VALID_CONTENT['dsa'])
+
+ fpw2 = FakePwEnt(pw_name='badguy', pw_dir='/tmp/home/badguy')
+ user_ssh_folder = "%s/.ssh" % fpw2.pw_dir
+ # /tmp/home/badguy/home/bobby = ""
+ authorized_keys2 = self.tmp_path('home/bobby', dir="/tmp/home/badguy")
+
+ # /tmp/etc/ssh/authorized_keys = ecdsa
+ authorized_keys_global = self.tmp_path('etc/ssh/authorized_keys2',
+ dir="/tmp")
+ util.write_file(authorized_keys_global, VALID_CONTENT['ecdsa'])
+
+ # /tmp/sshd_config
+ sshd_config = self.tmp_path('sshd_config', dir="/tmp")
+ util.write_file(
+ sshd_config,
+ "AuthorizedKeysFile %s %%h/.ssh/authorized_keys2 %s %s" %
+ (authorized_keys_global, user_keys, authorized_keys2)
+ )
+
+ # process first user
+ (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
+ fpw.pw_name, sshd_config)
+ content = ssh_util.update_authorized_keys(auth_key_entries, [])
+
+ self.assertEqual(user_keys, auth_key_fn)
+ self.assertTrue(VALID_CONTENT['rsa'] in content)
+ self.assertTrue(VALID_CONTENT['ecdsa'] in content)
+ self.assertTrue(VALID_CONTENT['dsa'] in content)
+
+ m_getpwnam.return_value = fpw2
+ # process second user
+ (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
+ fpw2.pw_name, sshd_config)
+ content = ssh_util.update_authorized_keys(auth_key_entries, [])
+
+ # badguy should not take the key from the other user!
+ self.assertEqual(authorized_keys2, auth_key_fn)
+ self.assertTrue(VALID_CONTENT['ecdsa'] in content)
self.assertTrue(VALID_CONTENT['dsa'] in content)
+ self.assertFalse(VALID_CONTENT['rsa'] in content)
# vi: ts=4 expandtab
--
2.27.0

View File

@ -1,98 +0,0 @@
From b84a1e6d246bbb758f0530038612bd18eff71767 Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Tue, 8 Dec 2020 13:27:22 +0100
Subject: [PATCH 4/4] ssh_util: handle non-default AuthorizedKeysFile config
(#586)
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
RH-MergeRequest: 28: ssh_util: handle non-default AuthorizedKeysFile config (#586)
RH-Commit: [1/1] f7ce396e3002c53a3504e653b58810efb956aa26 (eterrell/cloud-init)
RH-Bugzilla: 1862967
commit b0e73814db4027dba0b7dc0282e295b7f653325c
Author: Eduardo Otubo <otubo@redhat.com>
Date: Tue Oct 20 18:04:59 2020 +0200
ssh_util: handle non-default AuthorizedKeysFile config (#586)
The following commit merged all ssh keys into a default user file
`~/.ssh/authorized_keys` in sshd_config had multiple files configured for
AuthorizedKeysFile:
commit f1094b1a539044c0193165a41501480de0f8df14
Author: Eduardo Otubo <otubo@redhat.com>
Date: Thu Dec 5 17:37:35 2019 +0100
Multiple file fix for AuthorizedKeysFile config (#60)
This commit ignored the case when sshd_config would have a single file for
AuthorizedKeysFile, but a non default configuration, for example
`~/.ssh/authorized_keys_foobar`. In this case cloud-init would grab all keys
from this file and write a new one, the default `~/.ssh/authorized_keys`
causing the bug.
rhbz: #1862967
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
cloudinit/ssh_util.py | 6 +++---
tests/unittests/test_sshutil.py | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
index c08042d6..d5113996 100644
--- a/cloudinit/ssh_util.py
+++ b/cloudinit/ssh_util.py
@@ -262,13 +262,13 @@ def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG):
except (IOError, OSError):
# Give up and use a default key filename
- auth_key_fns[0] = default_authorizedkeys_file
+ auth_key_fns.append(default_authorizedkeys_file)
util.logexc(LOG, "Failed extracting 'AuthorizedKeysFile' in SSH "
"config from %r, using 'AuthorizedKeysFile' file "
"%r instead", DEF_SSHD_CFG, auth_key_fns[0])
- # always store all the keys in the user's private file
- return (default_authorizedkeys_file, parse_authorized_keys(auth_key_fns))
+ # always store all the keys in the first file configured on sshd_config
+ return (auth_key_fns[0], parse_authorized_keys(auth_key_fns))
def setup_user_keys(keys, username, options=None):
diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py
index fd1d1bac..88a111e3 100644
--- a/tests/unittests/test_sshutil.py
+++ b/tests/unittests/test_sshutil.py
@@ -593,7 +593,7 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
fpw.pw_name, sshd_config)
content = ssh_util.update_authorized_keys(auth_key_entries, [])
- self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
+ self.assertEqual(authorized_keys, auth_key_fn)
self.assertTrue(VALID_CONTENT['rsa'] in content)
self.assertTrue(VALID_CONTENT['dsa'] in content)
@@ -610,7 +610,7 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
sshd_config = self.tmp_path('sshd_config')
util.write_file(
sshd_config,
- "AuthorizedKeysFile %s %s" % (authorized_keys, user_keys)
+ "AuthorizedKeysFile %s %s" % (user_keys, authorized_keys)
)
(auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
@@ -618,7 +618,7 @@ class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
)
content = ssh_util.update_authorized_keys(auth_key_entries, [])
- self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn)
+ self.assertEqual(user_keys, auth_key_fn)
self.assertTrue(VALID_CONTENT['rsa'] in content)
self.assertTrue(VALID_CONTENT['dsa'] in content)
--
2.18.4

View File

@ -0,0 +1,369 @@
From 769b9f8c9b1ecc294a197575108ae7cb54ad7f4b Mon Sep 17 00:00:00 2001
From: Eduardo Otubo <otubo@redhat.com>
Date: Mon, 5 Jul 2021 14:13:45 +0200
Subject: [PATCH] write passwords only to serial console, lock down
cloud-init-output.log (#847)
RH-Author: Eduardo Otubo <otubo@redhat.com>
RH-MergeRequest: 21: write passwords only to serial console, lock down cloud-init-output.log (#847)
RH-Commit: [1/1] 8f30f2b7d0d6f9dca19994dbd0827b44e998f238 (otubo/cloud-init)
RH-Bugzilla: 1945891
RH-Acked-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
commit b794d426b9ab43ea9d6371477466070d86e10668
Author: Daniel Watkins <oddbloke@ubuntu.com>
Date: Fri Mar 19 10:06:42 2021 -0400
write passwords only to serial console, lock down cloud-init-output.log (#847)
Prior to this commit, when a user specified configuration which would
generate random passwords for users, cloud-init would cause those
passwords to be written to the serial console by emitting them on
stderr. In the default configuration, any stdout or stderr emitted by
cloud-init is also written to `/var/log/cloud-init-output.log`. This
file is world-readable, meaning that those randomly-generated passwords
were available to be read by any user with access to the system. This
presents an obvious security issue.
This commit responds to this issue in two ways:
* We address the direct issue by moving from writing the passwords to
sys.stderr to writing them directly to /dev/console (via
util.multi_log); this means that the passwords will never end up in
cloud-init-output.log
* To avoid future issues like this, we also modify the logging code so
that any files created in a log sink subprocess will only be
owner/group readable and, if it exists, will be owned by the adm
group. This results in `/var/log/cloud-init-output.log` no longer
being world-readable, meaning that if there are other parts of the
codebase that are emitting sensitive data intended for the serial
console, that data is no longer available to all users of the system.
LP: #1918303
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
---
cloudinit/config/cc_set_passwords.py | 5 +-
cloudinit/config/tests/test_set_passwords.py | 40 +++++++++----
cloudinit/tests/test_util.py | 56 +++++++++++++++++++
cloudinit/util.py | 38 +++++++++++--
.../modules/test_set_password.py | 24 ++++++++
tests/integration_tests/test_logging.py | 22 ++++++++
tests/unittests/test_util.py | 4 ++
7 files changed, 173 insertions(+), 16 deletions(-)
create mode 100644 tests/integration_tests/test_logging.py
diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py
index d6b5682d..433de751 100755
--- a/cloudinit/config/cc_set_passwords.py
+++ b/cloudinit/config/cc_set_passwords.py
@@ -78,7 +78,6 @@ password.
"""
import re
-import sys
from cloudinit.distros import ug_util
from cloudinit import log as logging
@@ -214,7 +213,9 @@ def handle(_name, cfg, cloud, log, args):
if len(randlist):
blurb = ("Set the following 'random' passwords\n",
'\n'.join(randlist))
- sys.stderr.write("%s\n%s\n" % blurb)
+ util.multi_log(
+ "%s\n%s\n" % blurb, stderr=False, fallback_to_stdout=False
+ )
if expire:
expired_users = []
diff --git a/cloudinit/config/tests/test_set_passwords.py b/cloudinit/config/tests/test_set_passwords.py
index daa1ef51..bbe2ee8f 100644
--- a/cloudinit/config/tests/test_set_passwords.py
+++ b/cloudinit/config/tests/test_set_passwords.py
@@ -74,10 +74,6 @@ class TestSetPasswordsHandle(CiTestCase):
with_logs = True
- def setUp(self):
- super(TestSetPasswordsHandle, self).setUp()
- self.add_patch('cloudinit.config.cc_set_passwords.sys.stderr', 'm_err')
-
def test_handle_on_empty_config(self, *args):
"""handle logs that no password has changed when config is empty."""
cloud = self.tmp_cloud(distro='ubuntu')
@@ -129,10 +125,12 @@ class TestSetPasswordsHandle(CiTestCase):
mock.call(['pw', 'usermod', 'ubuntu', '-p', '01-Jan-1970'])],
m_subp.call_args_list)
+ @mock.patch(MODPATH + "util.multi_log")
@mock.patch(MODPATH + "util.is_BSD")
@mock.patch(MODPATH + "subp.subp")
- def test_handle_on_chpasswd_list_creates_random_passwords(self, m_subp,
- m_is_bsd):
+ def test_handle_on_chpasswd_list_creates_random_passwords(
+ self, m_subp, m_is_bsd, m_multi_log
+ ):
"""handle parses command set random passwords."""
m_is_bsd.return_value = False
cloud = self.tmp_cloud(distro='ubuntu')
@@ -146,10 +144,32 @@ class TestSetPasswordsHandle(CiTestCase):
self.assertIn(
'DEBUG: Handling input for chpasswd as list.',
self.logs.getvalue())
- self.assertNotEqual(
- [mock.call(['chpasswd'],
- '\n'.join(valid_random_pwds) + '\n')],
- m_subp.call_args_list)
+
+ self.assertEqual(1, m_subp.call_count)
+ args, _kwargs = m_subp.call_args
+ self.assertEqual(["chpasswd"], args[0])
+
+ stdin = args[1]
+ user_pass = {
+ user: password
+ for user, password
+ in (line.split(":") for line in stdin.splitlines())
+ }
+
+ self.assertEqual(1, m_multi_log.call_count)
+ self.assertEqual(
+ mock.call(mock.ANY, stderr=False, fallback_to_stdout=False),
+ m_multi_log.call_args
+ )
+
+ self.assertEqual(set(["root", "ubuntu"]), set(user_pass.keys()))
+ written_lines = m_multi_log.call_args[0][0].splitlines()
+ for password in user_pass.values():
+ for line in written_lines:
+ if password in line:
+ break
+ else:
+ self.fail("Password not emitted to console")
# vi: ts=4 expandtab
diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py
index b7a302f1..e811917e 100644
--- a/cloudinit/tests/test_util.py
+++ b/cloudinit/tests/test_util.py
@@ -851,4 +851,60 @@ class TestEnsureFile:
assert "ab" == kwargs["omode"]
+@mock.patch("cloudinit.util.grp.getgrnam")
+@mock.patch("cloudinit.util.os.setgid")
+@mock.patch("cloudinit.util.os.umask")
+class TestRedirectOutputPreexecFn:
+ """This tests specifically the preexec_fn used in redirect_output."""
+
+ @pytest.fixture(params=["outfmt", "errfmt"])
+ def preexec_fn(self, request):
+ """A fixture to gather the preexec_fn used by redirect_output.
+
+ This enables simpler direct testing of it, and parameterises any tests
+ using it to cover both the stdout and stderr code paths.
+ """
+ test_string = "| piped output to invoke subprocess"
+ if request.param == "outfmt":
+ args = (test_string, None)
+ elif request.param == "errfmt":
+ args = (None, test_string)
+ with mock.patch("cloudinit.util.subprocess.Popen") as m_popen:
+ util.redirect_output(*args)
+
+ assert 1 == m_popen.call_count
+ _args, kwargs = m_popen.call_args
+ assert "preexec_fn" in kwargs, "preexec_fn not passed to Popen"
+ return kwargs["preexec_fn"]
+
+ def test_preexec_fn_sets_umask(
+ self, m_os_umask, _m_setgid, _m_getgrnam, preexec_fn
+ ):
+ """preexec_fn should set a mask that avoids world-readable files."""
+ preexec_fn()
+
+ assert [mock.call(0o037)] == m_os_umask.call_args_list
+
+ def test_preexec_fn_sets_group_id_if_adm_group_present(
+ self, _m_os_umask, m_setgid, m_getgrnam, preexec_fn
+ ):
+ """We should setgrp to adm if present, so files are owned by them."""
+ fake_group = mock.Mock(gr_gid=mock.sentinel.gr_gid)
+ m_getgrnam.return_value = fake_group
+
+ preexec_fn()
+
+ assert [mock.call("adm")] == m_getgrnam.call_args_list
+ assert [mock.call(mock.sentinel.gr_gid)] == m_setgid.call_args_list
+
+ def test_preexec_fn_handles_absent_adm_group_gracefully(
+ self, _m_os_umask, m_setgid, m_getgrnam, preexec_fn
+ ):
+ """We should handle an absent adm group gracefully."""
+ m_getgrnam.side_effect = KeyError("getgrnam(): name not found: 'adm'")
+
+ preexec_fn()
+
+ assert 0 == m_setgid.call_count
+
# vi: ts=4 expandtab
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 769f3425..4e0a72db 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -359,7 +359,7 @@ def find_modules(root_dir):
def multi_log(text, console=True, stderr=True,
- log=None, log_level=logging.DEBUG):
+ log=None, log_level=logging.DEBUG, fallback_to_stdout=True):
if stderr:
sys.stderr.write(text)
if console:
@@ -368,7 +368,7 @@ def multi_log(text, console=True, stderr=True,
with open(conpath, 'w') as wfh:
wfh.write(text)
wfh.flush()
- else:
+ elif fallback_to_stdout:
# A container may lack /dev/console (arguably a container bug). If
# it does not exist, then write output to stdout. this will result
# in duplicate stderr and stdout messages if stderr was True.
@@ -623,6 +623,26 @@ def redirect_output(outfmt, errfmt, o_out=None, o_err=None):
if not o_err:
o_err = sys.stderr
+ # pylint: disable=subprocess-popen-preexec-fn
+ def set_subprocess_umask_and_gid():
+ """Reconfigure umask and group ID to create output files securely.
+
+ This is passed to subprocess.Popen as preexec_fn, so it is executed in
+ the context of the newly-created process. It:
+
+ * sets the umask of the process so created files aren't world-readable
+ * if an adm group exists in the system, sets that as the process' GID
+ (so that the created file(s) are owned by root:adm)
+ """
+ os.umask(0o037)
+ try:
+ group_id = grp.getgrnam("adm").gr_gid
+ except KeyError:
+ # No adm group, don't set a group
+ pass
+ else:
+ os.setgid(group_id)
+
if outfmt:
LOG.debug("Redirecting %s to %s", o_out, outfmt)
(mode, arg) = outfmt.split(" ", 1)
@@ -632,7 +652,12 @@ def redirect_output(outfmt, errfmt, o_out=None, o_err=None):
owith = "wb"
new_fp = open(arg, owith)
elif mode == "|":
- proc = subprocess.Popen(arg, shell=True, stdin=subprocess.PIPE)
+ proc = subprocess.Popen(
+ arg,
+ shell=True,
+ stdin=subprocess.PIPE,
+ preexec_fn=set_subprocess_umask_and_gid,
+ )
new_fp = proc.stdin
else:
raise TypeError("Invalid type for output format: %s" % outfmt)
@@ -654,7 +679,12 @@ def redirect_output(outfmt, errfmt, o_out=None, o_err=None):
owith = "wb"
new_fp = open(arg, owith)
elif mode == "|":
- proc = subprocess.Popen(arg, shell=True, stdin=subprocess.PIPE)
+ proc = subprocess.Popen(
+ arg,
+ shell=True,
+ stdin=subprocess.PIPE,
+ preexec_fn=set_subprocess_umask_and_gid,
+ )
new_fp = proc.stdin
else:
raise TypeError("Invalid type for error format: %s" % errfmt)
diff --git a/tests/integration_tests/modules/test_set_password.py b/tests/integration_tests/modules/test_set_password.py
index b13f76fb..d7cf91a5 100644
--- a/tests/integration_tests/modules/test_set_password.py
+++ b/tests/integration_tests/modules/test_set_password.py
@@ -116,6 +116,30 @@ class Mixin:
# Which are not the same
assert shadow_users["harry"] != shadow_users["dick"]
+ def test_random_passwords_not_stored_in_cloud_init_output_log(
+ self, class_client
+ ):
+ """We should not emit passwords to the in-instance log file.
+
+ LP: #1918303
+ """
+ cloud_init_output = class_client.read_from_file(
+ "/var/log/cloud-init-output.log"
+ )
+ assert "dick:" not in cloud_init_output
+ assert "harry:" not in cloud_init_output
+
+ def test_random_passwords_emitted_to_serial_console(self, class_client):
+ """We should emit passwords to the serial console. (LP: #1918303)"""
+ try:
+ console_log = class_client.instance.console_log()
+ except NotImplementedError:
+ # Assume that an exception here means that we can't use the console
+ # log
+ pytest.skip("NotImplementedError when requesting console log")
+ assert "dick:" in console_log
+ assert "harry:" in console_log
+
def test_explicit_password_set_correctly(self, class_client):
"""Test that an explicitly-specified password is set correctly."""
shadow_users, _ = self._fetch_and_parse_etc_shadow(class_client)
diff --git a/tests/integration_tests/test_logging.py b/tests/integration_tests/test_logging.py
new file mode 100644
index 00000000..b31a0434
--- /dev/null
+++ b/tests/integration_tests/test_logging.py
@@ -0,0 +1,22 @@
+"""Integration tests relating to cloud-init's logging."""
+
+
+class TestVarLogCloudInitOutput:
+ """Integration tests relating to /var/log/cloud-init-output.log."""
+
+ def test_var_log_cloud_init_output_not_world_readable(self, client):
+ """
+ The log can contain sensitive data, it shouldn't be world-readable.
+
+ LP: #1918303
+ """
+ # Check the file exists
+ assert client.execute("test -f /var/log/cloud-init-output.log").ok
+
+ # Check its permissions are as we expect
+ perms, user, group = client.execute(
+ "stat -c %a:%U:%G /var/log/cloud-init-output.log"
+ ).split(":")
+ assert "640" == perms
+ assert "root" == user
+ assert "adm" == group
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index 857629f1..e5292001 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -572,6 +572,10 @@ class TestMultiLog(helpers.FilesystemMockingTestCase):
util.multi_log(logged_string)
self.assertEqual(logged_string, self.stdout.getvalue())
+ def test_logs_dont_go_to_stdout_if_fallback_to_stdout_is_false(self):
+ util.multi_log('something', fallback_to_stdout=False)
+ self.assertEqual('', self.stdout.getvalue())
+
def test_logs_go_to_log_if_given(self):
log = mock.MagicMock()
logged_string = 'something very important'
--
2.27.0

View File

@ -5,8 +5,8 @@
%global debug_package %{nil} %global debug_package %{nil}
Name: cloud-init Name: cloud-init
Version: 20.3 Version: 21.1
Release: 10%{?dist} Release: 7%{?dist}
Summary: Cloud instance init scripts Summary: Cloud instance init scripts
Group: System Environment/Base Group: System Environment/Base
@ -22,24 +22,18 @@ Patch0004: 0004-sysconfig-Don-t-write-BOOTPROTO-dhcp-for-ipv6-dhcp.patch
Patch0005: 0005-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch Patch0005: 0005-DataSourceAzure.py-use-hostnamectl-to-set-hostname.patch
Patch0006: 0006-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch Patch0006: 0006-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch
Patch0007: 0007-Remove-race-condition-between-cloud-init-and-Network.patch Patch0007: 0007-Remove-race-condition-between-cloud-init-and-Network.patch
Patch8: ci-Explicit-set-IPV6_AUTOCONF-and-IPV6_FORCE_ACCEPT_RA-.patch Patch0008: 0008-net-exclude-OVS-internal-interfaces-in-get_interface.patch
Patch9: ci-Add-config-modules-for-controlling-IBM-PowerVM-RMC.-.patch Patch0009: 0009-Fix-requiring-device-number-on-EC2-derivatives-836.patch
# For bz#1881462 - [rhel8][cloud-init] ifup bond0.504 Error: Connection activation failed: No suitable device found for this connection # For bz#1957532 - [cloud-init] From RHEL 82+ cloud-init no longer displays sshd keys fingerprints from instance launched from a backup image
Patch10: ci-network-Fix-type-and-respect-name-when-rendering-vla.patch Patch10: ci-rhel-cloud.cfg-remove-ssh_genkeytypes-in-settings.py.patch
# For bz#1859695 - [Cloud-init] DHCPv6 assigned address is not added to VM's interface # For bz#1945891 - CVE-2021-3429 cloud-init: randomly generated passwords logged in clear-text to world-readable file [rhel-8]
Patch11: ci-Adding-BOOTPROTO-dhcp-to-render-sysconfig-dhcp6-stat.patch Patch11: ci-write-passwords-only-to-serial-console-lock-down-clo.patch
# For bz#1898943 - [rhel-8]cloud-final.service fails if NetworkManager not installed.
Patch12: ci-Fix-unit-failure-of-cloud-final.service-if-NetworkMa.patch
# For bz#1862967 - [cloud-init]Customize ssh AuthorizedKeysFile causes login failure # For bz#1862967 - [cloud-init]Customize ssh AuthorizedKeysFile causes login failure
Patch13: ci-ssh_util-handle-non-default-AuthorizedKeysFile-confi.patch Patch12: ci-ssh-util-allow-cloudinit-to-merge-all-ssh-keys-into-.patch
# For bz#1859695 - [Cloud-init] DHCPv6 assigned address is not added to VM's interface # For bz#1862967 - [cloud-init]Customize ssh AuthorizedKeysFile causes login failure
Patch14: ci-Missing-IPV6_AUTOCONF-no-to-render-sysconfig-dhcp6-s.patch Patch13: ci-Stop-copying-ssh-system-keys-and-check-folder-permis.patch
# For bz#1900892 - [Azure] Update existing user password RHEL8x # For bz#1995840 - [cloudinit] Fix home permissions modified by ssh module
Patch15: ci-DataSourceAzure-update-password-for-defuser-if-exist.patch Patch14: ci-Fix-home-permissions-modified-by-ssh-module-SC-338-9.patch
# For bz#1919972 - [RHEL-8.4] ssh keys can be shared across users giving potential root access
Patch16: ci-Revert-ssh_util-handle-non-default-AuthorizedKeysFil.patch
# For bz#1913127 - A typo in cloud-init man page
Patch17: ci-fix-a-typo-in-man-page-cloud-init.1-752.patch
BuildArch: noarch BuildArch: noarch
@ -87,6 +81,7 @@ Requires: python3-six
Requires: shadow-utils Requires: shadow-utils
Requires: util-linux Requires: util-linux
Requires: xfsprogs Requires: xfsprogs
Requires: dhcp-client
%{?systemd_requires} %{?systemd_requires}
@ -193,8 +188,7 @@ if [ $1 -eq 0 ] ; then
fi fi
%postun %postun
%systemd_postun %systemd_postun cloud-config.service cloud-config.target cloud-final.service cloud-init.service cloud-init.target cloud-init-local.service
%files %files
%license LICENSE %license LICENSE
@ -231,6 +225,44 @@ fi
%config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf
%changelog %changelog
* Fri Aug 27 2021 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-7
- ci-Fix-home-permissions-modified-by-ssh-module-SC-338-9.patch [bz#1995840]
- Resolves: bz#1995840
([cloudinit] Fix home permissions modified by ssh module)
* Wed Aug 11 2021 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-6
- ci-Stop-copying-ssh-system-keys-and-check-folder-permis.patch [bz#1862967]
- Resolves: bz#1862967
([cloud-init]Customize ssh AuthorizedKeysFile causes login failure)
* Fri Aug 06 2021 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-5
- ci-Add-dhcp-client-as-a-dependency.patch [bz#1977385]
- Resolves: bz#1977385
([Azure][RHEL-8] cloud-init must require dhcp-client on Azure)
* Mon Jul 19 2021 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-4
- ci-ssh-util-allow-cloudinit-to-merge-all-ssh-keys-into-.patch [bz#1862967]
- Resolves: bz#1862967
([cloud-init]Customize ssh AuthorizedKeysFile causes login failure)
* Mon Jul 12 2021 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-3
- ci-write-passwords-only-to-serial-console-lock-down-clo.patch [bz#1945891]
- Resolves: bz#1945891
(CVE-2021-3429 cloud-init: randomly generated passwords logged in clear-text to world-readable file [rhel-8])
* Fri Jun 11 2021 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-2
- ci-rhel-cloud.cfg-remove-ssh_genkeytypes-in-settings.py.patch [bz#1957532]
- ci-cloud-init.spec.template-update-systemd_postun-param.patch [bz#1952089]
- Resolves: bz#1957532
([cloud-init] From RHEL 82+ cloud-init no longer displays sshd keys fingerprints from instance launched from a backup image)
- Resolves: bz#1952089
(cloud-init brew build fails on Fedora 33)
* Thu May 27 2021 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-1.el8
- Rebaes to 21.1 [bz#1958174]
- Resolves: bz#1958174
([RHEL-8.5.0] Rebase cloud-init to 21.1)
* Tue Feb 02 2021 Miroslav Rezanina <mrezanin@redhat.com> - 20.3-10.el8 * Tue Feb 02 2021 Miroslav Rezanina <mrezanin@redhat.com> - 20.3-10.el8
- ci-fix-a-typo-in-man-page-cloud-init.1-752.patch [bz#1913127] - ci-fix-a-typo-in-man-page-cloud-init.1-752.patch [bz#1913127]
- Resolves: bz#1913127 - Resolves: bz#1913127