2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-29 00:31:09 +00:00

Compare commits

..

332 Commits

Author SHA1 Message Date
Jarrod Johnson 700afc6606 Fix function definition
Sourcing the functions was causing a hang.
2020-06-01 11:12:28 -04:00
Jarrod Johnson e7cb3f9a0c Add the refactored files
The refactoring put the files in new places,
add those to tracking.
2020-06-01 10:52:38 -04:00
Jarrod Johnson 0254963432 Add some commentary to help users
Embed comments to help users understand
how to customize a bit better.
2020-06-01 10:52:01 -04:00
Jarrod Johnson 110fb27b2a Refactor the kickstart for EL8 a bit
Based on early feedback, working towards a less
intimidating script set for the sake of not
needing a 'postscripts' facility.
2020-06-01 09:08:41 -04:00
Jarrod Johnson 116682082a Fix reply on nonfunctional nic
If a nic was up but without IPv4,
do not reply even if the kernel is
'helping' us to be in the ballpark.
2020-05-29 17:32:45 -04:00
Jarrod Johnson 2c1ca170e5 Fix python3 issues in ssdp
In python2, they are already bytes.

In python 3, we need to check and convert if not already bytes.
2020-05-29 16:12:23 -04:00
Jarrod Johnson e648c9c065 Fix typo in pxe handler 2020-05-29 12:58:14 -04:00
Jarrod Johnson c1576e9787 Handle blank lines without error
Do not trigger syntax error on a blank expression.
2020-05-29 12:23:57 -04:00
Jarrod Johnson 6ee9ccbc27 Fix python3 idxtoname
python3 needs it to be str, not
bytes as C would return.
Accomodate only for python3, leave
python2 the same.
2020-05-29 12:06:00 -04:00
Jarrod Johnson 2a9d4e8079 Avoid breaking net config with '' DNS server
The node is probably misconfigured, but tolerate it
and carry on so that networking will at least
come up.
2020-05-29 08:58:33 -04:00
Jarrod Johnson 335d06ea4c Default to US if management node is n/a
localectl can be n/a for some scenarios,
default to US for such cases.
2020-05-28 17:07:17 -04:00
Jarrod Johnson 17b8000c0f Fix ethernet support
In accomodating the 20 byte
IB hwaddr, it could no longer
accept the 6 byte.

Adapt to all destination addresses up to 20.
2020-05-28 16:49:59 -04:00
Jarrod Johnson 5f170585c5 Adjust SUSE logic to work
Finding the apache configured
TLS cert location now works in
an easy configuration.
2020-05-28 16:47:36 -04:00
Jarrod Johnson 356de251ee Boot over IB support
IB is a bit different in broadcast
and certainly address length. Be
adaptive for those values.

Further, OPA port addr can get
mangled in the middle of a
transaction, recognize both the
full and mangled variant.
2020-05-28 15:37:30 -04:00
Jarrod Johnson 405bd84ce2 OS import fixes for python 2
Python 2 does not have some
of the tricks we were using, replace with python 2 friendly tricks.
2020-05-28 11:09:24 -04:00
Jarrod Johnson e9a14bd180 Have PXE content adapt to OS
Detect the respective paths
of tftp servers in the three supported distributions.
2020-05-27 17:05:04 -04:00
Jarrod Johnson c3fedf7309 Fix missing import isn osimage 2020-05-27 12:16:24 -04:00
Jarrod Johnson da8baa2ccb Fix missing libarchive dep
confluent now requires
libarchive to run.
2020-05-27 11:46:54 -04:00
Jarrod Johnson 4480d0418e Fix permissions of SSH CA key
It was creating CA as root, which
blocked confluent functionality.
2020-05-27 08:58:10 -04:00
Jarrod Johnson 987ac22b4e Fix permission issues with sshutil
uid management was done incorrectly
2020-05-22 18:13:00 -04:00
Jarrod Johnson 6471599bb2 Fix public/os permissions
When using osimage import,
the permissions on os directory
were too strict. Relax permissions
on public/os
2020-05-22 17:55:22 -04:00
Jarrod Johnson 05983201ee Fix mistaken use of $proto variable
Always use https to fetch the script
2020-05-22 15:59:19 -04:00
Jarrod Johnson b20517bd82 Add missing mtools dependency 2020-05-22 15:47:57 -04:00
Jarrod Johnson 4e8e44605a Merge branch 'master' into osdeploy 2020-05-22 15:47:04 -04:00
Jarrod Johnson d75867050c Fallback when pyte contains unknown colors
This allows nodeconsole to connect to Ubuntu
installer, for example.
2020-05-22 15:46:16 -04:00
Jarrod Johnson b4374817f5 Move getinstalldisk into profiles 2020-05-22 15:27:25 -04:00
Jarrod Johnson 968400d72b Update ssh known hosts and tftp
This is also required to get install going.
2020-05-22 15:03:56 -04:00
Jarrod Johnson 88436ff129 Merge branch 'master' into osdeploy 2020-05-22 13:56:59 -04:00
Jarrod Johnson 213d440052 Fix certificate watch
In python3, it was not working.
If another file got added, it could go
with a busy loop.
2020-05-22 13:55:24 -04:00
Jarrod Johnson eabf2073c1 Fix construction of site cpio
It must include directory names in correct
order for it to unpack correctly
2020-05-22 12:49:33 -04:00
Jarrod Johnson 906855ecf7 Fix cpio generation
It needed to be relative path, and also
fix use of bytes fed into cpio.
2020-05-22 11:40:54 -04:00
Jarrod Johnson 59e9ef2215 Create initramfs.cpio
This actually enables the site for
booting with site addons.

Use of a temporary name mitigates
problem if multiple collective members
try to init at same time.
2020-05-22 11:05:35 -04:00
Jarrod Johnson 021591017c Fix osimagetab completion 2020-05-22 09:03:02 -04:00
Jarrod Johnson ba0d600bf7 Prepare osimage initialize
Provide a helper for the various
setup steps required for
getting ready for OS deployment.
2020-05-21 17:07:58 -04:00
Jarrod Johnson eda645b792 Freshen up ssh and tls helpers
TLS will now work from scratch

SSH user key management updated to final design
2020-05-21 16:26:01 -04:00
Jarrod Johnson 8ce8f18f56 Change certutil to be a module
This allows it to be used from within
osimage command.
2020-05-21 15:56:16 -04:00
Jarrod Johnson 0af9db5eb7 Autodetect TLS key/cert locations
This facilitates easier getting started when possible.
2020-05-21 15:55:22 -04:00
Jarrod Johnson 22d5da3ae9 Rename certs to pem
There are contexts where the .cert name is not
recognized, go for .pem name instead.
2020-05-21 14:37:17 -04:00
Jarrod Johnson 6246d9e0a4 Default to never allowing insecure protocol
It erroneously used http rather than https
without user intervention.
2020-05-21 14:29:58 -04:00
Jarrod Johnson 726cb198c1 Actually list partitions in the HPC template
Without listing, autoyast fails to honor the disk somehow.
This causes disk to be honored. Additionally, use xfs instead of
btrfs and get rid of /home.
2020-05-21 10:57:19 -04:00
Jarrod Johnson b34f308c8a Merge branch 'master' into osdeploy 2020-05-21 08:48:27 -04:00
Jarrod Johnson 0800290c8e Fix checking colleective status
In a few contexts, the result of list_collective
was used directly in a boolean context. As an iterator,
it is always true unless coerced into a potentially empty
list explicitly.
2020-05-21 08:47:07 -04:00
Jarrod Johnson 51bc7dc88f Add nodedeploy command
This is akin to rinstall, but with
better tab completion and such
2020-05-20 16:21:08 -04:00
Jarrod Johnson 76952e774b Add more verbose autoconsole text
Point users towards screendump for Ubuntu and
SuSE.
2020-05-20 14:46:26 -04:00
Jarrod Johnson 8bff5c272b Actually modify meaningful grub config 2020-05-20 14:26:22 -04:00
Jarrod Johnson c587cd34ac Check correct location for deploycfg
This should fix the autocons behavior in
ubuntu.
2020-05-20 14:10:06 -04:00
Jarrod Johnson f8b27f1f8d Add autoconsole to ubunntu install
Have the installed system use serial console.
2020-05-20 13:24:05 -04:00
Jarrod Johnson 60453973cb Actually use the domain name in rhel deploy 2020-05-20 12:48:05 -04:00
Jarrod Johnson c48b023236 Inject DNS domain to suse deploy 2020-05-20 12:20:58 -04:00
Jarrod Johnson c0bd9f8bfb Insert domain into EL deploy
This hopefully sets search path up well.
2020-05-20 12:08:05 -04:00
Jarrod Johnson c4379e3fd2 Null domain becomes blank for ubuntu 2020-05-20 09:40:16 -04:00
Jarrod Johnson b0be352134 Correct target for TLS certs in ubuntu 2020-05-20 09:33:38 -04:00
Jarrod Johnson 7d4ce55127 Fix firstboot on Ubuntu
Correctly update deployment attributes upon completion.
2020-05-19 17:38:25 -04:00
Jarrod Johnson 9506817a00 Add DNS to DOMAINSEARCH, as ubuntu igores domain.. 2020-05-19 17:09:26 -04:00
Jarrod Johnson 0a79307cc5 Fix scope of keymap 2020-05-19 17:05:43 -04:00
Jarrod Johnson 442902ecd8 Fix mistake in selfservice api 2020-05-19 16:49:07 -04:00
Jarrod Johnson 5540896ab1 Convert Ubuntu to netconf syntax
The ipconfig syntax can't take domain. See how
it works using the same hooks that dhcp would have.
2020-05-19 16:23:36 -04:00
Jarrod Johnson e696566b42 Add dns domain to deployment
This permits the deployment profiles
to pull in specified DNS domain.
2020-05-19 15:07:10 -04:00
Jarrod Johnson eec7236110 Rename dns server attribute 2020-05-19 14:45:06 -04:00
Jarrod Johnson ebd41d3e53 Merge branch 'master' into osdeploy 2020-05-19 14:19:26 -04:00
Jarrod Johnson c5c5b36536 Fix reverse noderange in python 3
python3 environments do not natively know the word 'unicode',
in such a case, make unicode an alias for 'str'.
2020-05-19 14:18:35 -04:00
Jarrod Johnson 26caffab76 Have Ubuntu report deployment completion 2020-05-19 13:36:49 -04:00
Jarrod Johnson 9093a6e80a Rework ubuntu profile to break scripts into profile
This makes the pre/post/firstboot scripts more accessible
and consistent with the other distributions.
2020-05-19 12:22:46 -04:00
Jarrod Johnson 66f0e8225e Tighten permissions on confluent config files
Prior to copying them, make sure only root can interact.
The directory should protect them as well, but best to
not have anything controversial.
2020-05-19 10:51:32 -04:00
Jarrod Johnson 5f63875cae Finally fix SuSE certificate handling
It was targeting a symlink that went out of chroot,
target the underlying directory instead.
2020-05-19 10:38:31 -04:00
Jarrod Johnson 92f8acab7a Fix target path of tls anchors 2020-05-18 22:07:03 -04:00
Jarrod Johnson 540d0e1795 Fix SUSE certificate handling
SUSE requires things go through update-ca-certificates,
update the generated certs and the post to do that.
2020-05-18 19:37:54 -04:00
Jarrod Johnson b9cdf0d941 Move TLS certs into correct locations for SuSE 2020-05-18 17:00:05 -04:00
Jarrod Johnson dcb9f60ade Modify suse autocons logic 2020-05-18 16:13:16 -04:00
Jarrod Johnson d12ac8a1ba Try to adjust suse autoconsole behavior 2020-05-18 16:07:38 -04:00
Jarrod Johnson 14ba48da1f Add keymap to EL install 2020-05-18 14:55:35 -04:00
Jarrod Johnson ceefb936c2 Add keymap from donor system
RHEL requires this to autoinstall graphically.
2020-05-18 14:17:57 -04:00
Jarrod Johnson 9caf8056ec Correct split
bytes need bytes to split
2020-05-18 13:50:48 -04:00
Jarrod Johnson 317a1c572b Fix startswith parameter
Needed to be bytes for subprocess
2020-05-18 13:30:22 -04:00
Jarrod Johnson 2b39b9b5a6 Correct string in locale defaulting 2020-05-18 13:02:50 -04:00
Jarrod Johnson 5dee338d50 Fix lang in EL8 install
While text mode does not care, if user goes down graphical
path, it pauses installer.
2020-05-18 12:52:31 -04:00
Jarrod Johnson c459990457 Change EL8 to use autoconsole less 2020-05-18 11:46:47 -04:00
Jarrod Johnson 485b5e0dce Add textconsole to deploycfg
It turns out that the SPCR is
not automatically populated.
Provide a hint via deploycfg
as to whether the user has
configured confluent for text
console access.
2020-05-18 10:41:04 -04:00
Jarrod Johnson 188467ccf0 Correct order of EL post script
Actually try to fetch the file before copying it.
2020-05-16 12:12:30 -04:00
Jarrod Johnson dcda6a1080 Fix EL host based authentication 2020-05-16 11:43:38 -04:00
Jarrod Johnson 0c575456e3 Fix SuSE host authentication
curl was failing to get allnodes
2020-05-16 11:42:19 -04:00
Jarrod Johnson 2211755bf7 Have SUSE profile work for LEAP and SLE
Add a directive to comment out the superfluous autoyast
content from SLE when dealing with the more
straightforward LEAP.
2020-05-16 10:14:23 -04:00
Jarrod Johnson bf5727cab1 Disable yast self-update in suse
It slows down installs and shouldn't be needed.
2020-05-16 10:04:44 -04:00
Jarrod Johnson a05bfcec80 Fix suse15 firstboot
It was not using correct CA path nor was it filling in
authentication headers.
2020-05-15 20:13:06 -04:00
Jarrod Johnson 5ebe127db2 Fix Suse deployment
It missed the rename of the deploy_server attribute.
2020-05-15 19:11:22 -04:00
Jarrod Johnson b67140248e Change ipv4_server to deploy_server
Use a more neutral name for the deployment server target.
It is still ipv4 only for the moment, but easier to pull in
ipv6 support when not having to change paths that don't care.
2020-05-15 16:06:57 -04:00
Jarrod Johnson a5adcbd496 Add firstboot to EL
Also add 'mgt_server' rather than 'ipv4_server'. Will move to this.
The server should be able to dictate the right address without the
deploymennt image having to keep track.
2020-05-15 16:03:25 -04:00
Jarrod Johnson cab2310f35 Fix SLES Full Media
SLES can have Full media set,
support that as well as the
Installer/Packages split.
2020-05-15 14:54:34 -04:00
Jarrod Johnson 4d5ec98525 Correct SELinux context for firstboot
This permits the script to actually run at firstboot, even
if SELinux is on.
2020-05-14 18:24:35 -04:00
Jarrod Johnson 008bf33be3 Fix el8 prescript retrieval
Mistakenly was using the deployer
ip as profile.
2020-05-14 17:16:26 -04:00
Jarrod Johnson b52568dc10 Refactor RedHat/CentOS class profile
Again, break things up for more approachable layout.
2020-05-14 15:47:08 -04:00
Jarrod Johnson 24288f988a Call updatestatus upon completion of suse install 2020-05-14 14:49:53 -04:00
Jarrod Johnson 408d0a8673 Refactor SUSE to have external scripts
Try to improve readability of portions of install by
refactoring.
2020-05-14 13:36:23 -04:00
Jarrod Johnson 579e05972f Rename 'default' to hpc for suse
Since the profile is oriented toward installing
HPC template, name it more appropriately.
2020-05-14 13:05:38 -04:00
Jarrod Johnson f85693b7e3 Fix Headers in new replies
Needs to be a nested tuple.
2020-05-13 16:53:36 -04:00
Jarrod Johnson 7dcad26002 Add hook for registering install completion
This will allow watching the
profile for changes to know
when install is utterly complete.
2020-05-13 16:29:10 -04:00
Jarrod Johnson 393c3ac38e Fix distribution link for SUSE
SUSE needs to be pointed at the
parent directory, not the media
specific path.
2020-05-12 15:51:08 -04:00
Jarrod Johnson b73c561ca9 Support relative path import
Have osimage import correct to absolute
path for import.
2020-05-12 15:02:18 -04:00
Jarrod Johnson 1ba2386b82 Provide keyname to importer
This allows client to reliably make association
between import request and tracking
the import activity.
2020-05-12 14:47:00 -04:00
Jarrod Johnson 8a48909ae3 Relax permissions on osimage
Public content has to be world
readable for the web server
to provide it. Mitigate by only
temporarily relaxing umask.

Note that the known sensitive
portions are already doing an
explicit specification of permissions, so umask
relaxing won't affect those.
2020-05-12 13:41:54 -04:00
Jarrod Johnson 0b01ca59d2 Add osimage script to package 2020-05-12 12:14:27 -04:00
Jarrod Johnson 69d14564b0 Stop trying to modify the confluent lib
The confluent lib may be shared and not modifiable. This is fine.
2020-05-12 11:58:18 -04:00
Jarrod Johnson ccd94fb0cf Actually fix centos profiles 2020-05-12 11:50:45 -04:00
Jarrod Johnson bd91c58768 Fix CentOS profile generation
CentOS checker did not provide
category for profile population.
2020-05-12 11:26:04 -04:00
Jarrod Johnson 72c22939ad Merge branch 'master' into osdeploy 2020-05-12 10:32:40 -04:00
Jarrod Johnson 87a7e65b42 Add missing dependencies to deb package
A number of python packages were missing.
2020-05-12 10:31:36 -04:00
Jarrod Johnson 1beed070fd Add help text to usage 2020-05-12 09:53:46 -04:00
Jarrod Johnson 5addc7519d Rename osdeploy to osimage 2020-05-12 09:48:57 -04:00
Jarrod Johnson 7a68d1444b Extend osdeploy to have a config 2020-05-12 09:48:27 -04:00
Jarrod Johnson b5ccf9446a Fix importing task on existing folder
Allow deletion of a failed task.
2020-05-11 16:37:21 -04:00
Jarrod Johnson 807c68890d Fix import error
Attempted to make the same
directory twice.
2020-05-08 14:43:54 -04:00
Jarrod Johnson ee2f80b5d2 Merge branch 'master' into osdeploy 2020-05-08 14:27:25 -04:00
Jarrod Johnson a017c5460f Have confluent server able to import OS media
This wires up osimage to the API
such that it can work.
2020-05-08 14:27:04 -04:00
Jarrod Johnson 51c09d844f Fix broken expressions
Fix mistake in the {} password fix.
2020-05-08 13:29:33 -04:00
Jarrod Johnson b7c830b041 Fix mistake in listing profiles
Additionally, remove redundant
function definition.
2020-05-07 17:06:23 -04:00
Jarrod Johnson 853318feba Merge branch 'master' into osdeploy 2020-05-07 16:27:12 -04:00
Jarrod Johnson 2c4f8dfceb Fix backup/restore with python3
backup/restore with password
was having problems with python3
2020-05-07 16:27:00 -04:00
Jarrod Johnson 6fa863e8f9 Begin wiring up osimage to core 2020-05-07 16:26:27 -04:00
Jarrod Johnson d90e87e153 Fix backup/restore with python3
backup/restore with password
was having problems with python3
2020-05-07 16:22:56 -04:00
Jarrod Johnson b80647a36e Move osimport and tidy up
Tidy up a few issues with
osimage as it gets moved into place.
2020-05-07 14:08:38 -04:00
Jarrod Johnson 2c74cb18b0 Fix stray letter in file 2020-05-07 13:42:10 -04:00
Jarrod Johnson 8fe1cea2ed Quote sed argument 2020-05-07 12:13:44 -04:00
Jarrod Johnson 471f3bd1cf Fix ubuntu profile name
It was erroneously changed by
initprofile.sh
2020-05-07 12:10:27 -04:00
Jarrod Johnson f10bbdc33d Fix ubuntu missing osprofile
osprofile argument needed to
find path to install image.
2020-05-07 11:57:52 -04:00
Jarrod Johnson d6df9db229 Change efi/boot to lowercase
Consitently use lowercase for
efi/boot for easier grub handling.
2020-05-07 11:12:24 -04:00
Jarrod Johnson d5cab22f41 Add boot configuration generation
On osimport prototype, generate
boot media and config file
2020-05-07 11:08:28 -04:00
Jarrod Johnson 72d52c56b7 Fix build problem for osdeploy
addons.cpio was erroneously
stepping on itself.
2020-05-07 09:35:32 -04:00
Jarrod Johnson 4f2d4a7709 Move media content out of subdirectory
This paves the way for a consistent organization of boot
media
and network based boot.
2020-05-07 09:32:11 -04:00
Jarrod Johnson dcdd453112 Delete trailing whitespace 2020-05-07 09:30:31 -04:00
Jarrod Johnson 5a22168657 Touch up profile label for SUSE 2020-05-07 08:57:15 -04:00
Jarrod Johnson e18ca3dd94 Merge branch 'master' into osdeploy 2020-05-07 08:31:50 -04:00
Jarrod Johnson 3f53c55a66 Add custom port to nodeshell
Permit use of an alternative
port in nodeshell.
2020-05-07 08:27:16 -04:00
Jarrod Johnson 3e02e8bf85 Fix LEAP media detection
LEAP was incorrectly detected as SLE.
2020-05-06 16:56:35 -04:00
Jarrod Johnson cda0f439d3 Fix name replacement of Red Hat distributions 2020-05-06 15:55:31 -04:00
Jarrod Johnson e4bea785c3 Fix RHEL osimport init script 2020-05-06 15:31:29 -04:00
Jarrod Johnson 404041e226 Bring over disk boot content
This allows construction of remote media and httpboot payloads.
2020-05-06 14:33:45 -04:00
Jarrod Johnson 9180bab761 Example for XCC cert scripting 2020-05-06 14:23:54 -04:00
Jarrod Johnson 6d2918ed45 Extend osimport
Prepare osimport for inclusion in confluent server,
by making it break off intense activity to a subprocess.

Additionally, get it ready to track long running
task in a way that can map to api.

Finally, have it combine osdeploy
stock profiles with image
into install profiles.
2020-05-06 14:22:25 -04:00
Jarrod Johnson dd2dca5837 Add more profile contents
More parts of OS image profiles.
2020-05-06 14:03:53 -04:00
Jarrod Johnson 16b3bc44a0 Fix filename in ubuntu osdeploy 2020-05-05 09:53:03 -04:00
Jarrod Johnson e1e34f9f31 Adjust ubuntu architecture name
x86_64 is the confluent  convention
for 64 bit x86.
2020-05-05 09:26:42 -04:00
Jarrod Johnson e4a4bdf317 Fix clortho mistake
In attempting to correct clortho,
a mistake was made in the printf
formats.
2020-05-04 17:27:22 -04:00
Jarrod Johnson a4f9fdec3d Correctly target profile content in osdeploy build 2020-05-04 15:57:11 -04:00
Jarrod Johnson c3b295a4a2 Fix omitted prefix on variable 2020-05-04 15:55:22 -04:00
Jarrod Johnson 5539a6a1e8 Fix mistakes in the rpm template file 2020-05-04 15:53:29 -04:00
Jarrod Johnson bdd982e2ed Correct incorrect rpm build in osdeploy 2020-05-04 15:51:05 -04:00
Jarrod Johnson 1db9282058 Fix buildrpm script 2020-05-04 15:49:46 -04:00
Jarrod Johnson f7dcabc1ab Add buildrpm script to confluent_osdeploy 2020-05-04 15:47:39 -04:00
Jarrod Johnson efe936a93d Further build process for confluent_osdeploy 2020-05-04 15:45:35 -04:00
Jarrod Johnson 4619c466e5 Further rearrange osdeploy content 2020-05-04 15:25:04 -04:00
Jarrod Johnson 65797abfbb Move ubuntu into the deploy directory
Also get started on a spec file
2020-05-04 13:49:48 -04:00
Jarrod Johnson 2929e18413 Organize SUSE15 content 2020-05-04 12:53:05 -04:00
Jarrod Johnson cae75dbccf Rearrange EL8 content
Sort the samples into appropriate
locations.
2020-05-04 11:15:44 -04:00
Jarrod Johnson de201c9a3b Add clean to makefile
Additionally correct a small mistake
in copernicus.c
2020-05-04 10:39:07 -04:00
Jarrod Johnson f619b5933c Add makefile for compilation
The C utilities are easy enough to build
with a Makefile.
2020-05-04 10:22:59 -04:00
Jarrod Johnson cb1d5ebd13 Move compiled utilities and fix clortho
clortho had a number of compiler
warnings, make it compile cleanly.
2020-05-04 10:22:04 -04:00
Jarrod Johnson bbb4f5a0ec Add Ubuntu media to osimport 2020-05-03 19:18:12 -04:00
Jarrod Johnson 14192a6c21 Disable cloud-init and remove double-console line 2020-05-02 11:55:37 -04:00
Jarrod Johnson 530be7a508 Setup host based authentication in ssh in ubuntu 2020-05-02 10:58:06 -04:00
Jarrod Johnson 00eb72a627 Have ssh_known_hosts ready post install for ubuntu 2020-05-01 22:17:27 -04:00
Jarrod Johnson fa40793dfa Add install disk detection to ubuntu 2020-05-01 22:04:32 -04:00
Jarrod Johnson 3f8d825540 Add hook for installing disk
This will be used by pre.sh and getinstalldisk
to direct the install.
2020-05-01 19:33:47 -04:00
Jarrod Johnson 924102fa32 Add the cloud-init script
Still needs storage section, but surprisingly
not going to use much of this and instead lean
in on the scripts.
2020-05-01 16:47:07 -04:00
Jarrod Johnson f3747025ff Further the ubuntu prototype
Switch to using firstboot mechanism as cloud-init
fights us too hard on ssh keys and root password.
2020-05-01 16:41:01 -04:00
Jarrod Johnson 2ac139934e Add console to ubuntu 2020-05-01 10:10:31 -04:00
Jarrod Johnson b3b68774ab Add prototype ubuntu initrd additions
This sets the stage for Ubuntu deployment
2020-04-30 17:28:40 -04:00
Jarrod Johnson f27e6dfdda Rearrange OS content in a more orderly fashion 2020-04-30 17:27:25 -04:00
Jarrod Johnson 45eba14b10 Add some content from exploring suse15/el8 install 2020-04-28 19:43:47 -04:00
Jarrod Johnson 598ec4a294 Change ubuntu package names 2020-04-28 11:29:41 -04:00
Jarrod Johnson 501ab64e18 Revert "Add a utility to select disks"
This reverts commit 2936c7e8fd.
2020-04-27 17:37:21 -04:00
Jarrod Johnson 3437b8b03c Actually write out dhcp conf in initquee
dhcp was not written out during initqueue phase
2020-04-27 16:08:05 -04:00
Jarrod Johnson 2936c7e8fd Add a utility to select disks
Use python to enable a bit
more flexibility and still be
readable.
2020-04-24 09:40:55 -04:00
Jarrod Johnson 68251ffbb8 Fix dhcp mistake in the rhel initqueue
This prevented dhcp from ever being attempted
2020-04-22 16:12:33 -04:00
Jarrod Johnson 051b8259fd Update certutil to update site tls
This puts the certificate in a location
to be picked up by installers,
complete with subject_hash
so that SuSE can easily pull
them in.
2020-04-22 13:33:31 -04:00
Jarrod Johnson d691bcd306 Merge branch 'master' into osdeploy 2020-04-22 09:47:32 -04:00
Jarrod Johnson 36911c0d2e Merge branch 'master' into osdeploy 2020-04-22 09:01:02 -04:00
Jarrod Johnson 59aabb0e69 Fix apikey storage
Adapt to how crypted. values are handled.
2020-04-21 09:01:42 -04:00
Jarrod Johnson 931c7f25d1 Change apikey to crypted
Since it will be crypted, makes sense
for it to be presented same way as rootpassword.
2020-04-21 08:24:01 -04:00
Jarrod Johnson 149e41c33c Add DNS information
Enable setting DNS servers in
the deployment.
2020-04-20 14:27:14 -04:00
Jarrod Johnson 21327af140 Fix typo in the iniqueue script
gateway was not being honored.
2020-04-20 13:05:10 -04:00
Jarrod Johnson c21ae64f06 Fix PXE with static gateway
It was not working when gateway was set.
2020-04-20 12:45:13 -04:00
Jarrod Johnson eb2301b22e Add nodelist to selfservice
This permits a node to get list of nodes,
for example to build shosts.equiv
2020-04-20 11:11:29 -04:00
Jarrod Johnson 06d0e05dbb Set rootfs to have ssh ready
Get the CA and user key into the
right places to use in the target system.
2020-04-20 09:39:36 -04:00
Jarrod Johnson bfac51ba12 Prototype getinstalldisk
This script supersedes the bash script, hopefull
2020-04-17 16:37:04 -04:00
Jarrod Johnson 49a523ca5e Add timezone to deploycfg
This is generally needed to install
an operating system.
2020-04-17 13:21:15 -04:00
Jarrod Johnson 6798e4e848 Increase precision of copernicus time
While it is going to be off by some number of milliseconds,
it's better than being off by 250ms on average.
2020-04-17 12:07:48 -04:00
Jarrod Johnson 690c871d29 Merge branch 'master' into osdeploy 2020-04-17 10:01:55 -04:00
Jarrod Johnson 198b26245b Move hashing before collective
This results in consistent hash
using the initial random, rather
than equivalent, but odd looking
crypted hashes across collective.
2020-04-16 20:51:14 -04:00
Jarrod Johnson 4fb7924015 Fix invalid salts
base64 standard alphabet doesn't
quite map to salt alphabet.  Use
. and / instead.
2020-04-16 20:11:17 -04:00
Jarrod Johnson e4a9216683 Add root password to selfservice
This allows installers to set the
requested root password, or
not.
2020-04-16 17:07:31 -04:00
Jarrod Johnson dc2df09c4e Fix for crypted handling
Crypted attributes were not
handled well, fix the mistakes.
2020-04-16 16:54:59 -04:00
Jarrod Johnson 16667ed41c Add SSH cert to self api
This allows nodes to get a cert from
confluent.
2020-04-16 13:11:05 -04:00
Jarrod Johnson b37c034d6f Also carry in confluent.info
The installer needs this information to continue.
2020-04-16 12:40:35 -04:00
Jarrod Johnson 32038baa75 Carry deploycfg into installer 2020-04-16 12:23:45 -04:00
Jarrod Johnson dcdc8e4d5a Merge branch 'master' into osdeploy 2020-04-16 12:10:03 -04:00
Jarrod Johnson 4e8cc3d801 Carry API key past initramfs
This enables api calls during
the installer.
2020-04-16 11:54:40 -04:00
Jarrod Johnson 86a68bf7f9 Rework CA layout to file-per-CA
Have the deployed system combine
into known_hosts.  This simplifies
potential contention.
2020-04-16 10:08:27 -04:00
Jarrod Johnson 84988031a2 Move sshutil to a more practical place
This makes the keysigning function available to the server.
2020-04-16 09:49:30 -04:00
Jarrod Johnson 211b8ab7e8 Actually print message to serial
When console detected, actually notify
the relevant console.
2020-04-15 18:25:16 -04:00
Jarrod Johnson 23e8642950 Improve autocons
Provide more feedback and have
a proper cmdline style argument
2020-04-15 17:27:52 -04:00
Jarrod Johnson 76f7c12ca5 Incorporate autocons
This should permit skipping
it on kernel command line.
2020-04-15 16:50:33 -04:00
Jarrod Johnson e2aaa2afb4 Support proper readback of crypted
crypted attributes should be
treated about the same as
passwords.
2020-04-15 16:34:54 -04:00
Jarrod Johnson 890793068c Have prepivot stage accounts
Carry forward the ssh key and
disable passwords so that ssh
may safely be enabled during
install.
2020-04-15 16:19:23 -04:00
Jarrod Johnson 09700626b5 Add attribute to store root password
We want a non-recoverable form,
so hard code it to force it that way.
2020-04-15 16:18:39 -04:00
Jarrod Johnson 709ace4c92 Fix CA bundle in install
Once transitioning to install,
the CA bundle was lost, because
the symlink lead out of sysroot.

Fix by prepending sysroot if symlink
2020-04-15 13:30:47 -04:00
Jarrod Johnson 2c1ddabf32 Rename api.* attributes
Gather them into the deployment
category.
2020-04-15 11:50:10 -04:00
Jarrod Johnson 24bc1210d0 Numerous fixes to the OS deploy
Most notably, change to safe_dump for yaml.

For the various initrd items, changes
to actually trigger the relevant bits
of the RH installer.
2020-04-14 16:34:50 -04:00
Jarrod Johnson 276e01434d Remove example output from script 2020-04-10 16:49:14 -04:00
Jarrod Johnson 58fd760698 Implement begins of self api
This paves the way to get deployment started
in earnest.
2020-04-10 16:46:41 -04:00
Jarrod Johnson c76a0cfa16 Merge branch 'master' into osdeploy 2020-04-10 12:12:31 -04:00
Jarrod Johnson 2575e32209 Fix another python3 syntax problem
async can't even be a member of
a class, evidently.
2020-04-10 12:11:45 -04:00
Jarrod Johnson 989ae614c9 Merge branch 'master' into osdeploy 2020-04-10 12:09:41 -04:00
Jarrod Johnson 814209385c Fix another use of async name
For better python 3 compatibility,
stop using async as a variable name.
2020-04-10 12:08:55 -04:00
Jarrod Johnson 3b3475e073 Merge branch 'master' into osdeploy 2020-04-10 11:59:04 -04:00
Jarrod Johnson 331d10140a Fix httpapi with python3
async is now particularly special,
rename variable to fix it
2020-04-10 11:57:59 -04:00
Jarrod Johnson 2bd2946e9f Add time sync option to copernicus
Since we are dealing in TLS certificates,
the easiest thing is to have copernicus sync
time.  It is not as robust as ntp,
but it'll do as a stopgap
until the real time utilities
kick in.
2020-04-10 11:23:12 -04:00
Jarrod Johnson a3f7fc12b5 Include fe80 in cert
fe80 is needed and a valid
thing to assert.
2020-04-10 09:59:53 -04:00
Jarrod Johnson 5fb4f2b36c Add CAs from site to install
Preserve the CA situation into the installer
2020-04-09 16:18:39 -04:00
Jarrod Johnson 3ddeb4bcd0 A basic hook to force network bringup
initqueue must be satisfied by a udev rule.  We don't
know which network until iniqueue phase, but the hook
needs to install earlier.  This change induces
udev rule generation.
2020-04-09 16:14:30 -04:00
Jarrod Johnson 1722ad941b Add a starting sample of initqueue dracut hook
For rhel8, this initqueu hook is useful
2020-04-09 16:13:33 -04:00
Jarrod Johnson d18c0a576d Fix ProxyDHCP reply
Use the memoryview instead
of the bytearray for correct
use.
2020-04-09 09:20:34 -04:00
Jarrod Johnson acda061710 Merge branch 'master' into osdeploy 2020-04-09 08:21:24 -04:00
Jarrod Johnson fdd9c0953c Fix numerous python2 issues
python2 needs tobytes rather
than bytes of a memoryview.
2020-04-08 17:28:29 -04:00
Jarrod Johnson 137e35217b Implement UDP checksum
Properly implement checksum
on ACK.
2020-04-07 17:36:51 -04:00
Jarrod Johnson fe3d9da5aa Have a workable static PXE behavior
All but the checksum is now
functioning.
2020-04-07 17:07:07 -04:00
Jarrod Johnson d76c576b4e Merge branch 'master' into osdeploy 2020-04-07 11:35:03 -04:00
Jarrod Johnson f58b943cfe Fix mistakes in pxe handler
stringify was passed a string, fix
that input.

Fix mispelling of isinstance.
2020-04-06 17:11:23 -04:00
Jarrod Johnson 86628929de Merge branch 'master' into osdeploy 2020-04-06 14:51:01 -04:00
Jarrod Johnson b0a0bad635 Merge branch 'master' into osdeploy 2020-04-03 12:34:05 -04:00
Jarrod Johnson 1cfeed3f9d Merge branch 'master' into osdeploy 2020-04-03 11:02:14 -04:00
Jarrod Johnson 061e12beeb Merge branch 'master' into osdeploy 2020-04-02 11:43:32 -04:00
Jarrod Johnson 323be19f21 Fix typo in the legacy boot support 2020-04-02 11:34:42 -04:00
Jarrod Johnson 0983cacb20 Merge branch 'master' into osdeploy 2020-04-02 11:01:57 -04:00
Jarrod Johnson b3c878462f Integrate attributes into man pages 2020-04-02 11:00:45 -04:00
Jarrod Johnson 8e8e17a34e Merge branch 'master' into osdeploy 2020-04-02 10:26:28 -04:00
Jarrod Johnson e78b4e9f17 Merge branch 'master' into osdeploy 2020-04-02 10:18:11 -04:00
Jarrod Johnson ea537b1a1f Add branch to support legacy
Legacy boot gets into ipxe
through a kkpxe executable.
2020-04-02 09:41:17 -04:00
Jarrod Johnson bc7dc50388 Switch filename for iPXE
This offers the real iPXE script to
ipxe.
2020-04-01 17:19:23 -04:00
Jarrod Johnson 2da5bebf46 Fix iPXE recognition of our offer
iPXE requires that next-server
be set, unlike UEFI firmware
which did not care.
2020-04-01 16:59:43 -04:00
Jarrod Johnson 0fe136d4ee Extend a proxydhcpack
This is one stage of actually
providing download over
PXE.
2020-04-01 16:40:47 -04:00
Jarrod Johnson b573ffa897 Reduce log noise
Stop logging frequently occuring
events that are generally ignored.
2020-04-01 10:33:42 -04:00
Jarrod Johnson 0986853e6e Merge branch 'master' into osdeploy 2020-03-30 10:59:56 -04:00
Jarrod Johnson 96b5d3aa91 Actually populate the file field
Thankfully, it turns out that HTTPBoot carries the same flexibility
as PXE with respect to no-ip answer being merged with an IP answer.

As a bonus, it actually is simpler than PXE, allowing skipping of udp
port 4011.  Of course, we still want to support PXE boot, so that will
need to be implemented.
2020-03-27 16:17:51 -04:00
Jarrod Johnson 6cc0fd20a0 Merge branch 'master' into osdeploy 2020-03-27 12:14:56 -04:00
Jarrod Johnson 85c648925f Merge branch 'master' into osdeploy 2020-03-27 11:05:38 -04:00
Jarrod Johnson 44836cabbe Prepare for http boot
HTTP boot does not seem to support proxy dhcp.
2020-03-27 10:56:38 -04:00
Jarrod Johnson 92e657b987 Fix UDP checksum of odd-length packets
Odd length packets were incorrectly handled, fix
the packet construction.
2020-03-26 16:59:58 -04:00
Jarrod Johnson 16209bc3d6 Merge branch 'master' into osdeploy 2020-03-26 14:11:03 -04:00
Jarrod Johnson 41aa9e1cd2 Optimize chown
chown does not need to be called recursively every update/install.
2020-03-24 18:08:34 -04:00
Jarrod Johnson 0c96882fda Restore Python 3 compatibility for pxe
The changes accidentally broke python 3, make a
bytes explicit to fix.
2020-03-24 17:54:07 -04:00
Jarrod Johnson 00681489c7 Add python2 compatibility to pxe
Various facets of pxe.py were python3-only,
make it compatible with python2 for now.
2020-03-24 17:26:39 -04:00
Jarrod Johnson 8c2336b8fa Implemennt static when no DHCP
This permits an offer for a nominally static
node, unless it either doesn't map to an IP or
has dhcp method explicitly declared.
2020-03-24 15:09:28 -04:00
Jarrod Johnson a1bb603570 Fix multinet support and prefix 2020-03-24 10:31:19 -04:00
Jarrod Johnson 96cbfa5568 Prepare get_nic_config for servicing pxe
Ability to retrieve node network information according to context
of interface index that receives the packet.
2020-03-23 17:18:17 -04:00
Jarrod Johnson f0e5572b05 Remove filename from DHCP reply
filename is ignored if pxeclient anyway.  This will provide a
consistent approach for boot file for dhcp-elsewhere and
fixed ip clients alike.

Also add some TODO about how to enable DHCPREQUEST
processing when the time comes, as today the code skipping
option parsing for DISCOVER packets trips up the ability
to continue.  We must make sure we don't call discovery
handler again for REQUEST after doing so for DISCOVER.
2020-03-20 17:57:04 -04:00
Jarrod Johnson 895216d94f Setting PXEClient in option 60
This trigges the proxyDHCP sequence to follow up.
2020-03-20 17:40:29 -04:00
Jarrod Johnson 1c7c897267 Add option 97 (UUID) to OFFER
Per PXE specification, this is among the required fields.
2020-03-20 17:23:33 -04:00
Jarrod Johnson 20a26e6fdb Implement the UDP checksum
As long as we are doing UDP and IP in house, might as well do the
optional UDP checksum.
2020-03-20 16:55:20 -04:00
Jarrod Johnson 433f67730c Add RAW capability to the service
This is required for the new PXE support
2020-03-20 14:59:30 -04:00
Jarrod Johnson fcd73399fa Add IP and UDP header to OFFER attempt
It d oesn't quite work, but it looks convincing.  UDP checksum may
need to be added, and beyond that explore changes to the payload since
it is likely not enough DHCP was provided.
2020-03-19 18:39:32 -04:00
Jarrod Johnson b7d85b2166 Switch to ctypes for sendto
First, IP level sendto doesn't give the freedom to send 25.255.255.255
out of the interface desired.  So have to go link layer.

Second, while python can do sendto, we have the interface index number
from recvmsg (which was ctypes).  python expects the name instead
(wwhich it promptly turns... back into the index we start with).  I
could have added code to get the name using some syscalls which would
have been work and produced more system calls, or added the code to
directly use sendto, which would also be efficient at runtime.

So I chose to use ctypes for both recvmsg and sendto here.
2020-03-19 16:20:47 -04:00
Jarrod Johnson 22de1153c2 Continue draft of PXE support
It is able to vaguely construct a DHCPOFFER now, though it doesn't go
the right way for starters.
2020-03-18 17:12:26 -04:00
Jarrod Johnson e0223706b0 Add a sample CentOS/RH handler snippet
Begin work to prepare profiles for booting.
2020-03-16 18:03:07 -04:00
Jarrod Johnson 451ff6b5a3 Fix geometry in dir2img
The geometry was incorrect in dir2img, ensure the file is
a multiple of 512 bytes.
2020-03-16 14:47:21 -04:00
Jarrod Johnson b3c49c532c Rewrite dir2img in python
It is a good measure to prep for more heavy usage in
OS deployment.

While changes were being made anyway, also cut time to make
an image in half.
2020-03-16 12:52:18 -04:00
Jarrod Johnson 8fb206b1f7 Fix cert util on pythoen3
python 3 had bytes and not str and need to be
decoded before string operations.
2020-03-16 09:27:34 -04:00
Jarrod Johnson 1bf7c6970f Merge branch 'master' into osdeploy 2020-03-13 11:06:26 -04:00
Jarrod Johnson 6ade0952c7 Workaround incorrect TLS clients
Standards compliant TLS clients require
that IP addresses be compared against
IP type SAN fields.

However, some firmware ignores IP fields and only checks DNS fields.

Workaround and provide compatibility
by duplicating the IP as DNS and IP fields.

Also, clean up the temporary config file when done.
2020-03-12 19:06:05 -04:00
Jarrod Johnson 517101f596 Reduce spurious logging
When ignoring DHCPREQUEST,
do not log, since DISCOVER
was enough logging.
2020-03-12 18:08:48 -04:00
Jarrod Johnson 1bfc949466 Add message when insecure is needed
Practically speaking, this will be only choice for most people, but we
must make sure that the user acknowledges the risk.
2020-03-12 17:42:56 -04:00
Jarrod Johnson e97214ca50 Merge branch 'master' into osdeploy 2020-03-12 16:05:08 -04:00
Jarrod Johnson 57ff9808c4 Fix infinite loop in DHCP parsing
Accidentally failed to actually traverse
the data.
2020-03-11 15:43:34 -04:00
Jarrod Johnson 0f67f5c382 Draft attempt at refactoring PXE
The plan is to have 'discovery'
data only on DISCOVER, but
still parse and be able to react to
REQUEST packets.

Additionally add the attributes
to control deployment state and
permissible protocols.
2020-03-11 15:22:45 -04:00
Jarrod Johnson b789252c9c Merge branch 'master' into osdeploy 2020-03-11 14:00:52 -04:00
Jarrod Johnson d38b06224c Merge branch 'master' into osdeploy 2020-03-11 09:30:18 -04:00
Jarrod Johnson 9ea9188fdf Get closer to replying to PXE
Monitor and maintain a uuid and
mac map to node in preparation for
efficient handling of
DISCOVERs/REQUESTS from
ONIE and/or PXE.
2020-03-10 11:16:59 -04:00
Jarrod Johnson 597393842a Add transaction ID to the carried information
The ability to extend an OFFER requires that we also keep track
of the transaction id.
2020-03-09 15:30:26 -04:00
Jarrod Johnson b9fc9b3c19 Put the recvmsg info into the handler payload
This allows the rest of the discovery infrastructure to operate
with this data.
2020-03-09 15:14:05 -04:00
Jarrod Johnson 49b8e12a01 Update TODO
We have now done IP_PKTINFO, but there is a potential TODO
if wanting to properly honor unicast request.

For now, will just do always-broadcast as the options to do unicast
are more complex to do (either construct the packets or inject neigh
entry manually)
2020-03-09 15:05:19 -04:00
Jarrod Johnson f20fb70336 Have the peer and target info on DHCP receive
This has the data that will be handy in formulating a reply.
2020-03-09 14:39:53 -04:00
Jarrod Johnson 934f8f0f20 Use recvmsg for PXE
Switch to recvmsg in preparation for getting more
data for forming replies.
2020-03-09 11:25:38 -04:00
Jarrod Johnson 945b8f2b4a Rename to reflect more function than CA 2020-03-09 08:52:29 -04:00
Jarrod Johnson 82921fb53d Add function to sign SSH key
This will enable the known_hosts
to work.

shosts.equiv and sshd and ssh client
config will be handled elsewhere.
shosts.equiv will just be everything.
2020-03-06 16:55:06 -05:00
Jarrod Johnson 59a0b00208 Flesh out the SSH code more
Notably add user key management
and start poking things in
/var/lib/confluent
2020-03-06 16:17:53 -05:00
Jarrod Johnson 34f2f6e359 Add a sample for doing SSH CA
This will explore the concept for the
backend of the get certificate api.
2020-03-06 13:43:54 -05:00
Jarrod Johnson 7fe47baab3 Fix another python 3 expectation
python3 needs this stringified from socket that
provides bytes.
2020-03-02 16:42:42 -05:00
Jarrod Johnson 3c1453c16b Actually use the de-lla address
After removing the %, actually use the trimmed address.
2020-03-02 16:23:18 -05:00
Jarrod Johnson 4529924cce Fix credserver python3 and LLA support
Both client and server had an issue with LLA, along with the
usual python3-isms.
2020-03-02 16:06:07 -05:00
Jarrod Johnson 97ddd59dbd Merge branch 'osdeploy' of github.com:jjohnson42/confluent into osdeploy 2020-03-02 14:55:11 -05:00
Jarrod Johnson b7b2522f6b Fix python3 compatibility of ssdp
The ssdp module in osdeploy was not reacting
adequately to copernicus.  fix the assumptions about bytes
versus str.
2020-03-02 14:54:23 -05:00
Jarrod Johnson bd0e187525 Merge branch 'master' into osdeploy 2020-03-02 13:15:22 -05:00
Jarrod Johnson 455b637c48 Merge branch 'master' into osdeploy 2020-03-02 11:30:30 -05:00
Jarrod Johnson e257d526c3 Have ip_on_same_subnet normalize ::ffff: addresses
This will cause the ips to count as equivalent rather than giving up
on them.
2020-02-27 17:03:12 -05:00
Jarrod Johnson a066f061c7 Remove IPv6 portion of IPv4 address
If it is presented as an IPv6 compatible IPv4 address,
make it a normal IP address.
2020-02-27 16:59:32 -05:00
Jarrod Johnson 29b4045817 Omit TRANS.TBL files from osimport
Avoid clutter of TRANS.TBL files while importing an ISO
2020-02-27 16:36:47 -05:00
Jarrod Johnson f798239f90 Switch to using the standard confluent port for credserver
Also add a check and only accept API arming
requests from local ips
2020-02-27 16:36:16 -05:00
Jarrod Johnson f955086cc3 Create an alternative api.armed behavior
Move from a clock based expiration to a simpler 'once' versus
'continous' model.  'once' is intended to be used generally, 'continuous'
for stateless without benefit of TPM.  The goal would be to use TPM
to seal a key to avoid continuous.
2020-02-27 13:33:05 -05:00
Jarrod Johnson cd20a23626 Merge branch 'master' into osdeploy 2020-02-27 07:20:20 -05:00
Jarrod Johnson 54be209f4e Merge branch 'nodesearch' into osdeploy 2020-02-24 16:26:07 -05:00
Jarrod Johnson 114324f513 Add CA to self signed cert constraints
Some applications require this be set for it to work
as an enrolled certificate.  Notably UEFI
requires this.
2020-02-24 15:34:55 -05:00
Jarrod Johnson d2de4ffa14 Fix single file OS image osimport 2020-02-21 14:25:18 -05:00
Jarrod Johnson d4483bb59f Polish up the osimport concept more 2020-02-21 14:18:15 -05:00
Jarrod Johnson 90bec92d1f Fix python3 for os import
Need to be explicit about binary data with python 3.
2020-02-21 09:34:49 -05:00
Jarrod Johnson 4b3541e21d Suppress libarchive logging
libarchive ffi goes crazy logging at *import* time.  Pre-empt
use of the logging with a null handler prior to import.
2020-02-21 08:46:42 -05:00
Jarrod Johnson 737e7a440f Add a prototype for imageimporter
This is a sample of fingerprinting, covering rhel/centos 7/8
and suse enterprise 12/15 and cumulus.

Mainly to run the gamut of detection schemes.

The schemes are for iso images, try to be very careful and adaptive.

Otherwise, go for a quick sum to see if we have a shot and a long checksum to confirm.
2020-02-20 23:24:42 -05:00
Jarrod Johnson 24874bb4be Merge branch 'master' into nodesearch 2020-02-20 20:37:23 -05:00
Jarrod Johnson 45c13a3d46 Merge branch 'master' into nodesearch 2019-10-17 13:46:23 -04:00
Jarrod Johnson a04eea6927 Merge branch 'master' into nodesearch 2019-10-17 09:31:01 -04:00
Jarrod Johnson f46939b7ec Add missing comma to api attributes 2019-10-16 13:50:23 -04:00
Jarrod Johnson 17a8ab3211 Use python3 explicitly for building source
The source distrobution on CentOS7 does not work well
with python2.  Using python3 to build the dist for python2
and python3 fixes issues with extra data being missed in
packaging.
2019-10-16 13:25:21 -04:00
Jarrod Johnson 0fd4c3b2f7 Merge branch 'master' into nodesearch 2019-10-16 13:24:11 -04:00
Jarrod Johnson 829a5b08c1 Merge branch 'master' into nodesearch 2019-09-06 10:09:48 -04:00
Jarrod Johnson aa059c6a4d Amend some formatting 2019-08-12 16:19:35 -04:00
Jarrod Johnson 2e03b662ea Add a credential server implelmentation
This implements the api.armed logic and storing key when
using the clortho credential agent.
2019-08-12 15:31:24 -04:00
Jarrod Johnson 55a0aab548 Add node api key and arming
This is the groundwork for having node authentication.
The intent is for calling code to modify api.armed if
the administrator wants to opt into a one-time set of
credential.

This design as is currently does not fit a stateless
deploy model.  That may suggest an additional manual step
for a fully stateless model.  Alternatively adding support
for credential persistence through sealing to a node's TPM,
which would allow more freely retrievable node credentials.
2019-08-12 15:28:00 -04:00
Jarrod Johnson aaf5aebff7 Fix for tokens with null bytes
Since the server may employ the full range of byte values
in the echo token, use that length and the buffer to avoid
nulls truncating the token.
2019-08-12 15:24:51 -04:00
Jarrod Johnson c1abeaff04 Convert concept to IP based
This leaves the door open for routing if supported.

The server shall restrict IP_TTL to denote
acceptable distance from the manager to accept.
2019-08-09 16:43:48 -04:00
Jarrod Johnson 37d4543d24 Merge branch 'master' into nodesearch 2019-08-08 16:18:21 -04:00
Jarrod Johnson d13e286609 Merge branch 'master' into nodesearch 2019-07-30 14:46:43 -04:00
Jarrod Johnson a7c93627e2 Merge branch 'master' into nodesearch 2019-07-23 08:18:44 -04:00
Jarrod Johnson 6e5cfe69a8 Merge branch 'master' into nodesearch 2019-07-22 09:41:45 -04:00
Jarrod Johnson 626f1c16e2 Merge branch 'nodesearch' of github.com:jjohnson42/confluent into nodesearch 2019-07-19 14:10:13 -04:00
Jarrod Johnson 43480c2e3b Fix nodesensors -n with csv 2019-07-19 14:09:51 -04:00
Jarrod Johnson b91693a973 Merge branch 'master' into nodesearch 2019-07-17 13:05:20 -04:00
Jarrod Johnson da8b4d00d8 Merge branch 'master' into nodesearch 2019-07-16 18:46:52 -04:00
Jarrod Johnson b7b7fd82eb ECHO a packet back to manager
In a later phase, we will want assurances
that the neighbor table was populated.
Since here we have the sockaddr handy,
it makes a lot of sense to take the
opportunity to blind fire a packet back.
No reply is expected, just enough to
trigger arp/neighbor solicitation.
2019-07-16 07:51:24 -04:00
Jarrod Johnson 3789e43f35 Merge branch 'master' into nodesearch 2019-07-16 07:32:00 -04:00
Jarrod Johnson a325dcb423 Merge branch 'nodesearch' of github.com:jjohnson42/confluent into nodesearch 2019-07-15 11:09:29 -04:00
Jarrod Johnson c8d0009dac Avoid copernicus printout of more duplicate data
Separate v4 and v6 results for better chance of success in
dropping duplicate packets.
2019-07-15 11:09:20 -04:00
Jarrod Johnson 79f5dce6dc Implement node search for confluent
This is a viable client to find and get ones node identity.

Node credentials are a separate concern, to be handled later.
2019-07-15 11:09:20 -04:00
Jarrod Johnson 54f36e259f Avoid copernicus printout of more duplicate data
Separate v4 and v6 results for better chance of success in
dropping duplicate packets.
2019-07-15 10:30:36 -04:00
Jarrod Johnson acf67a6c81 Implement node search for confluent
This is a viable client to find and get ones node identity.

Node credentials are a separate concern, to be handled later.
2019-07-12 17:04:14 -04:00
76 changed files with 3904 additions and 340 deletions
+37 -16
View File
@@ -1,19 +1,40 @@
#!/bin/sh
#!/usr/bin/python
# This will take a given directory and make a 'big floppy image'
# out of it, suitable for nodemedia upload.
if [ -z "$1" -o -z "$2" ]; then
echo "Usage: $0 <directory> <imagefile>"
exit 1
fi
# Get the needed payload side
SIZE=$(du -sB 512 $1|awk '{print $1}')
ENTRIES=$(find $1 |wc -l)
# Also, each file and directory has overhead, pad size by 32kb per file,
# which should be overkill but other values proved to be inadequate
# A deeper understanding of VFAT would probably allow for more precise value
SIZE=$((SIZE + ENTRIES * 64))
dd if=/dev/zero of=$2 bs=512 count=$SIZE
# Make a big single sided floppy with many many tracks, saves on complex math
mformat -i $2 -d 1 -t $SIZE -s 1 -h 1 ::
mcopy -i $2 -s $1/* ::
import glob
import os
import subprocess
import sys
def create_image(directory, image):
ents = 0
datasz = 512
for dir in os.walk(sys.argv[1]):
ents += 1
for filen in dir[2]:
ents += 1
filename = os.path.join(dir[0], filen)
currsz = os.path.getsize(filename)
# assuming up to 65k cluster
currsz = (currsz // 512 +1) * 512
datasz += currsz
datasz += ents * 32768
datasz = datasz // 512 + 1
with open(image, 'wb') as imgfile:
imgfile.seek(datasz * 512 - 1)
imgfile.write(b'\x00')
subprocess.check_call(['mformat', '-i', image, '-d', '1', '-t',
str(datasz), '-s', '1','-h', '1', '::'])
cpycmd = ['mcopy', '-i', image, '-s']
cpycmd.extend(glob.glob('{0}/*'.format(directory)))
cpycmd.append('::')
subprocess.check_call(cpycmd)
if __name__ == '__main__':
if len(sys.argv) < 3:
sys.stderr.write("Usage: {0} <directory> <imagefile>".format(
sys.argv[0]))
sys.exit(1)
create_image(sys.argv[1], sys.argv[2])
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/python3
import argparse
import os
import sys
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
def armonce(nr, cli):
nodes = set([])
for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)):
for node in rsp.get('databynode', {}):
nodeinfo = rsp['databynode'][node]
for attr in nodeinfo:
if attr == 'deployment.apiarmed':
curr = nodeinfo[attr].get('value', '')
if curr == 'continuous':
nodes.add(node)
noderange = nr
if nodes:
noderange += ',-({0})'.format(','.join(nodes))
for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange),
{'deployment.apiarmed': 'once'}):
pass
def setpending(nr, profile, cli):
for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
{'deployment.pendingprofile': profile}):
pass
def main(args):
ap = argparse.ArgumentParser(description='Deploy OS to nodes')
ap.add_argument('-n', '--network', help='Initiate deployment over PXE', action='store_true')
ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed')
ap.add_argument('noderange', help='Set of nodes to deploy')
ap.add_argument('profile', help='Profile name to deploy')
args = ap.parse_args(args)
if not args.network:
sys.stderr.write('Currently only network (-n) deployment is supported\n')
return 1
c = client.Command()
c.stop_if_noderange_over(args.noderange, args.maxnodes)
armonce(args.noderange, c)
setpending(args.noderange, args.profile, c)
errnodes = set([])
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
bootmode='uefi',
persistent=False,
errnodes=errnodes)
if errnodes:
sys.stderr.write(
'Unable to set boot device for following nodes: {0}\n'.format(
','.join(errnodes)))
return 1
rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot')
return rc
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
+8 -3
View File
@@ -46,10 +46,12 @@ def run():
help='Number of commands to run at a time')
argparser.add_option('-n', '--nonodeprefix', action='store_true',
help='Do not prefix output with node names')
argparser.add_option('-p', '--port', type='int', default=0,
help='Specify a custom port for ssh')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
'prompting if over the threshold')
'prompting if over the threshold')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
argparser.disable_interspersed_args()
@@ -59,7 +61,7 @@ def run():
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
c = client.Command()
cmdstr = " ".join(args[1:])
currprocs = 0
@@ -79,7 +81,10 @@ def run():
cmd = ex[node]['value']
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
cmdv = ['ssh', node, cmd]
if options.port:
cmdv = ['ssh', '-p', '{0}'.format(options.port), node, cmd]
else:
cmdv = ['ssh', node, cmd]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
+31
View File
@@ -19,6 +19,7 @@ alias nodebmcreset='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export
alias nodeboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeboot'
alias nodeconfig='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconfig'
alias nodeconsole='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconsole'
alias nodedeploy='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedeploy'
alias nodedefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedefine'
alias nodeeventlog='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeeventlog'
alias nodefirmware='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodefirmware'
@@ -137,6 +138,34 @@ _confluent_nodefirmware_completion()
fi
}
_confluent_osimage_completion()
{
_confluent_get_args
if [ $NUMARGS == 2 ]; then
COMPREPLY=($(compgen -W "initialize import" -- ${COMP_WORDS[COMP_CWORD]}))
return
elif [ ${CMPARGS[1]} == 'initialize' ]; then
COMPREPLY=($(compgen -W "-h -u -s -t -i" -- ${COMP_WORDS[COMP_CWORD]}))
elif [ ${CMPARGS[1]} == 'import' ]; then
compopt -o default
COMPREPLY=()
return
fi
}
_confluent_nodedeploy_completion()
{
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
COMPREPLY=($(compgen -W "-n $(confetty show /deployment/profiles|sed -e 's/\///')" -- "${COMP_WORDS[COMP_CWORD]}"))
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodeshell_completion()
{
_confluent_get_args
@@ -266,6 +295,8 @@ complete -F _confluent_nr_completion nodeconfig
complete -F _confluent_nn_completion nodeconsole
complete -F _confluent_nr_completion nodeeventlog
complete -F _confluent_nodefirmware_completion nodefirmware
complete -F _confluent_nodedeploy_completion nodedeploy
complete -F _confluent_osimage_completion osimage
complete -F _confluent_ng_completion nodegroupattrib
complete -F _confluent_ng_completion nodegroupremove
complete -F _confluent_nr_completion nodehealth
+11
View File
@@ -0,0 +1,11 @@
VERSION=`git describe|cut -d- -f 1`
NUMCOMMITS=`git describe|cut -d- -f 2`
if [ "$NUMCOMMITS" != "$VERSION" ]; then
VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3`
fi
sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy.spec.tmpl > confluent_osdeploy.spec
cd ..
tar Jcvf confluent_osdeploy.tar.xz confluent_osdeploy
mv confluent_osdeploy.tar.xz ~/rpmbuild/SOURCES/
cd -
rpmbuild -ba confluent_osdeploy.spec
@@ -0,0 +1,47 @@
Name: confluent_osdeploy-x86_64
Version: #VERSION#
Release: 1
Summary: OS Deployment support for confluent
License: Apache2
URL: http://hpc.lenovo.com/
Source0: confluent_osdeploy.tar.xz
BuildArch: noarch
Requires: confluent_ipxe mtools
BuildRoot: /tmp
%description
This contains support utilities for enabling deployment of x86_64 architecture systems
%define debug_package %{nil}
%prep
%setup -n confluent_osdeploy
%build
mkdir -p opt/confluent/bin
cd utils
make all
cp copernicus clortho autocons ../opt/confluent/bin
cd ..
for os in el8 suse15 ubuntu20.04; do
mkdir ${os}out
cd ${os}out
cp -a ../opt .
cp -a ../${os}/initramfs/* .
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
cd ..
done
%install
for os in el8 suse15 ubuntu20.04; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
cp ${os}out/addons.cpio %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
cp -a $os/profiles/* %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
done
%files
/opt/confluent/lib/osdeploy
@@ -0,0 +1,15 @@
#!/bin/bash
echo -n "" >> /tmp/net.ifaces
cat /tls/*.0 >> /etc/pki/tls/certs/ca-bundle.crt
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
if [ -n "$autocons" ]; then
echo console=$autocons |sed -e 's!/dev/!!' >> /tmp/01-autocons.conf
autocons=${autocons%,*}
echo $autocons > /tmp/01-autocons.devnode
echo "Detected firmware specified console at $(cat /tmp/01-autocons.conf)" > $autocons
echo "Initializing auto detected console when installer starts" > $autocons
fi
fi
. /lib/anaconda-lib.sh
wait_for_kickstart
@@ -0,0 +1,96 @@
#!/bin/sh
[ -e /tmp/confluent.initq ] && return 0
echo -n "" > /tmp/confluent.initq
cd /sys/class/net
for currif in *; do
ip link set $currif up
done
cd -
while ! grep MANAGER /tmp/confluent.info >& /dev/null; do
/opt/confluent/bin/copernicus -t > /tmp/confluent.info
done
read ifidx <<EOF
$(grep ^MANAGER /tmp/confluent.info|grep fe80|sed -e s/.*%//)
EOF
read mgr << EOF
$(grep ^MANAGER /tmp/confluent.info|grep fe80|awk '{print $2}')
EOF
mgridx=${mgr#*%}
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
echo $ifname > /tmp/net.ifaces
nodename=$(grep ^NODENAME /tmp/confluent.info|awk '{print $2}')
#TODO: blkid --label <whatever> to find mounted api
if [ -z "$apikey" ]; then
apikey=$(/opt/confluent/bin/clortho $nodename $mgr)
fi
oum=$(umask)
umask 0077
echo $apikey > /etc/confluent.apikey
umask $oum
mgr="[$mgr]"
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/deploycfg > /tmp/confluent.deploycfg
dnsdomain=$(grep ^dnsdomain: /tmp/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
hostname=$nodename
if [ ! -z "$dnsdomain" ] && [ "$dnsdomain" != "null" ]; then
hostname=$hostname.$dnsdomain
fi
mgr=$(grep ^deploy_server: /tmp/confluent.deploycfg)
mgr=${mgr#deploy_server: }
profilename=$(grep ^profile: /tmp/confluent.deploycfg)
profilename=${profilename#profile: }
proto=$(grep ^protocol: /tmp/confluent.deploycfg)
proto=${proto#protocol: }
textconsole=$(grep ^textconsole: /tmp/confluent.deploycfg)
textconsole=${textconsole#textconsole: }
if [ $textconsole = "true" ] && ! grep console= /proc/cmdline > /dev/null; then
autocons=$(cat /tmp/01-autocons.devnode)
if [ ! -z "$autocons" ]; then
echo Auto-configuring installed system to use text console
echo Auto-configuring installed system to use text console > $autocons
cp /tmp/01-autocons.conf /etc/cmdline.d/
else
echo "Unable to automatically detect requested text console"
fi
fi
echo inst.repo=$proto://$mgr/confluent-public/os/$profilename/distribution >> /etc/cmdline.d/01-confluent.conf
echo inst.ks=$proto://$mgr/confluent-public/os/$profilename/kickstart >> /etc/cmdline.d/01-confluent.conf
kickstart=$proto://$mgr/confluent-public/os/$profilename/kickstart
root=anaconda-net:$proto://$mgr/confluent-public/os/$profilename/distribution
export kickstart
export root
autoconfigmethod=$(grep ipv4_method /tmp/confluent.deploycfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "dhcp" ]; then
echo ip=$ifname:dhcp >> /etc/cmdline.d/01-confluent.conf
else
v4addr=$(grep ^ipv4_address: /tmp/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /tmp/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ipv4_netmask: /tmp/confluent.deploycfg)
v4nm=${v4nm#ipv4_netmask: }
echo ip=$v4addr::$v4gw:$v4nm:$hostname:$ifname:none >> /etc/cmdline.d/01-confluent.conf
fi
nameserversec=0
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]] && [[ $entry != "- ''" ]]; then
echo nameserver=${entry#- } >> /etc/cmdline.d/01-confluent.conf
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /tmp/confluent.deploycfg
@@ -0,0 +1,23 @@
#!/bin/bash
BUNDLENAME=/sysroot/etc/pki/tls/certs/ca-bundle.crt
while [ -h $BUNDLENAME ]; do
BUNDLENAME=/sysroot/$(readlink $BUNDLENAME)
done
cat /etc/pki/tls/certs/ca-bundle.crt > $BUNDLENAME
mkdir -p /sysroot/etc/confluent/
cp -a /tls /sysroot/etc/confluent
sed -i 's/install::/install:*:/' /sysroot/etc/shadow
sed -i 's/root::/root:*:/' /sysroot/etc/shadow
mkdir -p /sysroot/root/.ssh
chmod 700 /sysroot/root/.ssh
cat /ssh/*.rootpubkey > /sysroot/root/.ssh/authorized_keys
chmod 600 /sysroot/root/.ssh/authorized_keys
mkdir -p /sysroot/etc/ssh/
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
cp /etc/confluent.apikey /sysroot/etc/
cp /etc/confluent.apikey /sysroot/etc/confluent/
cp /tmp/confluent.deploycfg /tmp/confluent.info /sysroot/etc/
cp /tmp/confluent.deploycfg /tmp/confluent.info /sysroot/etc/confluent
@@ -0,0 +1,7 @@
#!/bin/sh
sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/' $2/profile.yaml
ln -s $1/images/pxeboot/vmlinuz $2/boot/kernel && \
ln -s $1/images/pxeboot/initrd.img $2/boot/initramfs/distribution
mkdir -p $2/boot/efi/boot && \
ln -s $1/EFI/BOOT/BOOTX64.EFI $1/EFI/BOOT/grubx64.efi $2/boot/efi/boot/
@@ -0,0 +1,65 @@
# In this OS profile, data is largely filled in during the %pre
# phase, rather than the kickstart actually having the content.
# None of the files shall be replaced during an upgrade in
# /var/lib/confluent/public/os/<profile>, so customization should
# be done by modifying files in /var/lib/confluent/public/os/<profile>
# /tmp/rootpw will provide a 'rootpw' line, either locking password if not configured
# or the crypted form.
%include /tmp/rootpw
# timezone is fetched from confluent server, which provides the
# timezone that the management server itself is in by default.
%include /tmp/timezone
# similar to timezone, confluent is asked to provide the
# deployment servers language info and replicate that
# to the deployment target.
%include /tmp/langinfo
# The default partition scheme is applied to a single drive, using
# the getinstalldisk script to make a best guess as to the most
# appropriate device. See pre.sh and getinstalldisk to customize
# the automatic behavior, or comment out/delete the
# following line and provide your own manual partition plan
# instead
%include /tmp/partitioning
reboot
%packages
@^minimal-environment
chrony
rsync
python3
%end
%pre
profile=$(grep ^profile: /etc/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/preinst.sh
. /tmp/preinst.sh
%end
%post --nochroot
mkdir -p /mnt/sysimage/etc/confluent
profile=$(grep ^profile: /etc/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/prechroot.sh > /tmp/postinst.sh
. /tmp/postinst.sh
# Hook firstboot.sh
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.service > /mnt/sysimage/etc/systemd/system/firstboot.service
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /mnt/sysimage/etc/confluent/firstboot.sh
chmod +x /mnt/sysimage/etc/confluent/firstboot.sh
%end
%post
systemctl enable firstboot
chgrp ssh_keys /etc/ssh/ssh*key
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /etc/confluent/firstboot.sh
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/postinst.sh
. /tmp/postinst.sh
%end
@@ -0,0 +1,2 @@
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet
@@ -0,0 +1,11 @@
[Unit]
Description=First Boot Process
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/etc/confluent/firstboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,7 @@
#!/bin/sh
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
curl --capath /etc/confluent/tls -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
systemctl disable firstboot
rm /etc/systemd/system/firstboot.service
@@ -0,0 +1,14 @@
run_remote() {
cd $(mktemp -d)
curl -f https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
chmod +x $1
./$1
cd -
}
run_remote_python() {
cd $(mktemp -d)
curl -f https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
/usr/libexec/platform-python $1
cd -
}
@@ -0,0 +1,88 @@
import subprocess
import os
class DiskInfo(object):
def __init__(self, devname):
self.name = devname
self.wwn = None
self.path = None
self.model = ''
self.size = 0
self.driver = None
self.mdcontainer = ''
devnode = '/dev/{0}'.format(devname)
qprop = subprocess.check_output(
['udevadm', 'info', '--query=property', devnode])
if not isinstance(qprop, str):
qprop = qprop.decode('utf8')
for prop in qprop.split('\n'):
if '=' not in prop:
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
self.path = v
elif k == 'ID_WWN':
self.wwn = v
elif k == 'MD_CONTAINER':
self.mdcontainer = v
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
if not isinstance(attrs, str):
attrs = attrs.decode('utf8')
for attr in attrs.split('\n'):
if '==' not in attr:
continue
k, v = attr.split('==', 1)
k = k.strip()
if k == 'ATTRS{size}':
self.size = v.replace('"', '')
elif (k == 'DRIVERS' and not self.driver
and v not in ('"sd"', '""')):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2'):
return 0
if 'imsm' in self.mdcontainer:
return 1
if self.driver == 'ahci':
return 2
if self.driver.startswith('megaraid'):
return 3
if self.driver.startswith('mpt'):
return 4
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def main():
disks = []
for disk in sorted(os.listdir('/sys/class/block')):
try:
disk = DiskInfo(disk)
disks.append(disk)
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
if __name__ == '__main__':
main()
@@ -0,0 +1,19 @@
#!/bin/sh
# need to copy over ssh key info
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
export mgr profile nodename
. /etc/confluent/functions
# This script will execute in the installed system, but using the installer kernel prior to reboot.
# This is an appropriate place to run post install activities that do not require the actual installed
# kernel to run. For example adding drivers that would be needed for first boot to run cleanly.
# If, for example, there is a post script that has a dependency on a driver or filesystem that
# cannot work until booting into the installer, use firstboot.sh instead
# run_remote will download and execute from /var/lib/confluent/public/<os>/scripts/ directory
# run_remote_python will use the appropriate python interpreter path to run the specified script
# Add content as below:
# run_remote example.sh
# run_remote_python example.py
@@ -0,0 +1,45 @@
#!/bin/sh
# This runs prior to the installer beginning. This is used to rewrite the
# scripted install file, merging data from confluennt and identifying
# the most appropriate install source.
# If you want to use a more custom partition plan, the easiest
# method is to edit the kicktstart file and comment out or
# delete %include /tmp/partitioning
nodename=$(grep ^NODENAME /etc/confluent.info|awk '{print $2}')
locale=$(grep ^locale: /etc/confluent.deploycfg)
locale=${locale#locale: }
keymap=$(grep ^keymap: /etc/confluent.deploycfg)
keymap=${keymap#keymap: }
echo lang $locale > /tmp/langinfo
echo keyboard --vckeymap=$keymap >> /tmp/langinfo
tz=$(grep ^timezone: /etc/confluent.deploycfg)
tz=${tz#timezone: }
echo timezone $tz --utc > /tmp/timezone
rootpw=$(grep ^rootpassword /etc/confluent.deploycfg | awk '{print $2}')
if [ "$rootpw" = null ]; then
echo "rootpw --lock" > /tmp/rootpw
else
echo "rootpw --iscrypted $rootpw" > /tmp/rootpw
fi
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent.apikey)" -d @$pubkey https://$mgr/confluent-api/self/sshcert > $certfile
echo HostCertificate $certfile >> /etc/ssh/sshd_config.anaconda
done
/usr/sbin/sshd -f /etc/ssh/sshd_config.anaconda
if [ -f "/run/install/cmdline.d/01-autocons.conf" ]; then
consoledev=$(cat /run/install/cmdline.d/01-autocons.conf | sed -e 's!console=!/dev/!' -e 's/,.*//')
tmux a <> $consoledev >&0 2>&1 &
fi
export mgr profile nodename
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /tmp/functions
. /tmp/functions
run_remote_python getinstalldisk
if [ -e /tmp/installdisk ]; then
echo clearpart --all --initlabel >> /tmp/partitioning
echo ignoredisk --only-use $(cat /tmp/installdisk) >> /tmp/partitioning
echo autopart --nohome >> /tmp/partitioning
fi
@@ -0,0 +1,19 @@
#!/bin/sh
# This script runs after install is complete, but inside the installer
# environment. This is useful for carrying work done in pre/during the
# installer into the installed environment.
# It is almost certainly more useful to use post.sh or firstboot.sh
# for customization, which will run in a more normal mechanism
nodename=$(grep ^NODENAME /etc/confluent.info|awk '{print $2}')
export mgr profile nodename
cp -a /etc/confluent /mnt/sysimage/etc
cp /tmp/functions /mnt/sysimage/etc/confluent/
. /tmp/functions
# Preserve the ssh setup work done for the installer
# by copying into the target system and setting up
# host based authentication
run_remote setupssh.sh
@@ -0,0 +1,23 @@
#!/bin/sh
grep HostCert /etc/ssh/sshd_config.anaconda >> /mnt/sysimage/etc/ssh/sshd_config
echo HostbasedAuthentication yes >> /mnt/sysimage/etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /mnt/sysimage/etc/ssh/sshd_config
echo IgnoreRhosts no >> /mnt/sysimage/etc/ssh/sshd_config
sshconf=/etc/ssh/ssh_config
if [ -d /mnt/sysimage/etc/ssh/ssh_config.d/ ]; then
sshconf=/mnt/sysimage/etc/ssh/ssh_config.d/01-confluent.conf
fi
echo 'Host *' >> $sshconf
echo ' HostbasedAuthentication yes' >> $sshconf
echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
cp /etc/ssh/ssh_host_* /mnt/sysimage/etc/ssh/
mkdir /mnt/sysimage/root/.ssh/
chmod 700 /mnt/sysimage/root/.ssh/
cp /root/.ssh/authorized_keys /mnt/sysimage/root/.ssh/
chmod 600 /mnt/sysimage/root/.ssh/authorized_keys
cp /etc/ssh/ssh_known_hosts /mnt/sysimage/etc/ssh/
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent.apikey)" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
cp /tmp/allnodes /mnt/sysimage/etc/ssh/shosts.equiv
cp /tmp/allnodes /mnt/sysimage/root/.shosts
@@ -0,0 +1 @@
Install: exec:/opt/confluent/bin/suseagent
@@ -0,0 +1,106 @@
#!/bin/bash
echo "Installing certificates"
echo '<authorized_keys xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns" config:type="list">' > /tmp/rootkeys.xml
for pub in /ssh/*.rootpubkey; do
echo '<listentry>'$(cat $pub)'</listentry>' >> /tmp/rootkeys.xml
done
echo '</authorized_keys>' >> /tmp/rootkeys.xml
/usr/bin/cp /tls/*.0 /var/lib/ca-certificates/openssl/
/usr/bin/cp /tls/*.0 /etc/ssl/certs/
echo "LineMode: 1" > /etc/linuxrc.d/01-confluent
autocons=""
if ! grep console /proc/cmdline > /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
if [ ! -z "$autocons" ]; then
echo "Serial console detected from firmmware: $autocons" > ${autocons%,*}
fi
fi
cd /sys/class/net
for nic in *; do
ip link set $nic up
done
echo -n "Discovering confluent..."
/opt/confluent/bin/copernicus -t > /tmp/confluent.info
while ! grep MANAGER: /tmp/confluent.info > /dev/null; do
/opt/confluent/bin/copernicus -t > /tmp/confluent.info
done
nodename=$(grep ^NODENAME: /tmp/confluent.info | head -n 1 | sed -e 's/NODENAME: //')
echo "done ($nodename)"
echo "Hostname: $nodename" >> /etc/linuxrc.d/01-confluent
mgr=$(grep ^MANAGER: /tmp/confluent.info | head -n 1 | sed -e 's/MANAGER: //')
echo -n "Acquiring configuration from $mgr..."
bootifidx=${mgr#*%}
for nic in *; do
if [ "$(cat $nic/ifindex)" = "$bootifidx" ]; then
bootif=$nic
fi
done
cd -
echo "NetDevice: $bootif" >> /etc/linuxrc.d/01-confluent
/opt/confluent/bin/clortho $nodename $mgr > /tmp/confluent.apikey
mgr="[$mgr]"
curl -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /tmp/confluent.apikey)" https://$mgr/confluent-api/self/deploycfg > /tmp/confluent.deploycfg
dnsdomain=$(grep ^dnsdomain: /tmp/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
if [ ! -z "$dnsdomain" ] && [ "$dnsdomain" != "null" ]; then
echo "Domain: $dnsdomain" >> /etc/linuxrc.d/01-confluent
fi
textconsole=$(grep ^textconsole: /tmp/confluent.deploycfg)
textconsole=${textconsole#textconsole: }
if [ "$textconsole" = "true" ] && ! grep console= /proc/cmdline > /dev/null && [ ! -z "$autocons" ]; then
echo "Serial console autodetected and enabled, will be available upon install completion" > ${autocons%,*}
echo "The installer will run in text mode on the graphics console. When ssh is available," > ${autocons%,*}
echo "install progress may be checked by using ssh to access and run the screendump command" > ${autocons%,*}
echo "Install time serial console would require the profile to have console=${autocons#/dev/}" > ${autocons%,*}
echo "specified in the kernel command line arguments" > ${autocons%,*}
sed -e s'/$/ 'console=${autocons#*/dev/}/ /proc/cmdline > /etc/fakecmdline
mount -o bind /etc/fakecmdline /proc/cmdline
echo "ConsoleDevice: ${autocons%,*}" >> /etc/linuxrc.d/01-confluent
fi
tz=$(grep timezone: /tmp/confluent.deploycfg | awk '{print $2}')
echo "<timezone>${tz}</timezone>" > /tmp/timezone
autoconfigmethod=$(grep ipv4_method /tmp/confluent.deploycfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "dhcp" ]; then
echo "DHCP: 1" >> /etc/linuxrc.d/01-confluent
else
v4addr=$(grep ^ipv4_address: /tmp/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /tmp/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
v4nm=$(grep ipv4_netmask: /tmp/confluent.deploycfg)
v4nm=${v4nm#ipv4_netmask: }
echo "HostIP: $v4addr" >> /etc/linuxrc.d/01-confluent
echo "Netmask: $v4nm" >> /etc/linuxrc.d/01-confluent
if [ "$v4gw" != "null" ]; then
echo "Gateway: $v4gw" >> /etc/linuxrc.d/01-confluent
fi
nameserversec=0
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]]; then
echo Nameserver: ${entry#- } >> /etc/linuxrc.d/01-confluent
continue
fi
fi
nameserversec=0
if [ ${entry%:*} = "nameservers" ]; then
nameserversec=1
continue
fi
done < /tmp/confluent.deploycfg
fi
echo done
mgr=$(grep ^deploy_server: /tmp/confluent.deploycfg)
mgr=${mgr#deploy_server: }
profilename=$(grep ^profile: /tmp/confluent.deploycfg)
profilename=${profilename#profile: }
proto=$(grep ^protocol: /tmp/confluent.deploycfg)
proto=${proto#protocol: }
echo "<media_url>${proto}://${mgr}/confluent-public/os/${profilename}/product</media_url>" > /tmp/pkgurl
echo "AutoYaST: $proto://$mgr/confluent-public/os/$profilename/autoyast" >> /etc/linuxrc.d/01-confluent
echo "Install: $proto://$mgr/confluent-public/os/$profilename/distribution/1" >> /etc/linuxrc.d/01-confluent
exec /init
@@ -0,0 +1,135 @@
<?xml version="1.0"?>
<!DOCTYPE profile SYSTEM "/usr/share/YaST2/include/autoinstall/profile.dtd">
<profile xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns" xmlns:xi="http://www.w3.org/2001/XInclude">
<general>
<self_update config:type="boolean">false</self_update>
<clock>
<hwclock>UTC</hwclock>
<xi:include href="file:///tmp/timezone"/>
</clock>
<mode>
<confirm config:type="boolean">false</confirm>
</mode>
</general>
%%IFSLE%%
<add-on>
<add_on_products config:type="list">
<listentry><xi:include href="file:///tmp/pkgurl"/><product>sle-module-basesystem</product><product_dir>/Module-Basesystem</product_dir></listentry>
<listentry><xi:include href="file:///tmp/pkgurl"/><product>sle-module-hpc</product><product_dir>/Module-HPC</product_dir></listentry>
<listentry><xi:include href="file:///tmp/pkgurl"/><product>sle-module-server-applications</product><product_dir>/Module-Server-Applications</product_dir></listentry>
<listentry><xi:include href="file:///tmp/pkgurl"/><product>sle-module-containers</product><product_dir>/Module-Containers</product_dir></listentry>
<listentry><xi:include href="file:///tmp/pkgurl"/><product>SLE_HPC</product><product_dir>/Product-HPC</product_dir></listentry>
<listentry><xi:include href="file:///tmp/pkgurl"/><product>Legacy-Module</product><product_dir>/Module-Legacy</product_dir></listentry>
</add_on_products>
</add-on>
%%ENDIFSLE%%
<software>
%%IFSLE%%
<products config:type="list">
<product>SLE_HPC</product>
</products>
%%ENDIFSLE%%
<patterns config:type="list">
<pattern>base</pattern>
</patterns>
<packages config:type="list">
<package>openssl</package>
<package>chrony</package>
<package>rsync</package>
<package>screen</package>
<package>vim</package>
<package>binutils</package>
<package>pciutils</package>
<package>usbutils</package>
<package>nfs-client</package>
<package>ethtool</package>
</packages>
</software>
<partitioning config:type="list">
<drive>
<device>%%INSTDISK%%</device>
<initialize config:type="boolean">true</initialize>
<use>all</use>
<partitions config:type="list">
<partition>
<filesystem config:type="symbol">xfs</filesystem>
<mount>/</mount>
<size>max</size>
</partition>
<partition>
<mount>swap</mount>
<size>auto</size>
</partition>
<partition>
<mount>/boot</mount>
<size>500M</size>
</partition>
</partitions>
</drive>
</partitioning>
<users config:type="list">
<user>
<username>root</username>
<user_password>%%ROOTPASSWORD%%</user_password>
<encrypted config:type="boolean">true</encrypted>
<xi:include href="file:///tmp/rootkeys.xml"/>
</user>
</users>
<networking>
<dns>
<hostname>%%NODENAME%%</hostname>
</dns>
<keep_install_network config:type="boolean">true</keep_install_network>
</networking>
<services-manager>
<services>
<enable config:type="list">
<service>sshd</service>
</enable>
</services>
</services-manager>
<scripts>
<pre-scripts config:type="list">
<script>
<filename>preinstall.sh</filename>
<interpreter>shell</interpreter>
<source>
<![CDATA[
#!/bin/sh
mgr=$(grep ^deploy_server /tmp/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /tmp/confluent.deploycfg|sed -e 's/^profile: //')
proto=$(grep ^protocol: /tmp/confluent.deploycfg |awk '{print $2}')
curl $proto://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/pre.sh
. /tmp/pre.sh
]]>
</source>
</script>
</pre-scripts>
<chroot-scripts config:type="list">
<script>
<filename>chroot.sh</filename>
<interpreter>shell</interpreter>
<source>
<![CDATA[
#!/bin/sh
mgr=$(grep ^deploy_server /tmp/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /tmp/confluent.deploycfg|sed -e 's/^profile: //')
proto=$(grep ^protocol: /tmp/confluent.deploycfg |awk '{print $2}')
curl $proto://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/post.sh
. /tmp/post.sh
curl $proto://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /mnt/etc/confluent/firstboot.sh
chmod +x /mnt/etc/confluent/firstboot.sh
]]>
</source>
</script>
</chroot-scripts>
<init-scripts config:type="list">
<script>
<filename>post.sh</filename>
<interpreter>shell</interpreter>
<location>file:///etc/confluent/firstboot.sh</location>
</script>
</init-scripts>
</scripts>
</profile>
@@ -0,0 +1,20 @@
#!/bin/sh
discnum=$(basename $1)
if [ "$discnum" != 1 ]; then exit 0; fi
if [ -e $2/boot/kernel ]; then exit 0; fi
if ls $1/Product-* >& /dev/null; then
ln -s $1 $2/product
else
ln -s ${1%1}2 $2/product
fi
sed -i 's/sle 15/SUSE Linux Enterprise 15/; s/opensuse_leap/openSUSE Leap/' $2/profile.yaml
ln -s $1/boot/x86_64/loader/linux $2/boot/kernel && \
ln -s $1/boot/x86_64/loader/initrd $2/boot/initramfs/distribution && \
mkdir -p $2/boot/efi/boot && \
ln -s $1/EFI/BOOT/bootx64.efi $1/EFI/BOOT/grub.efi $2/boot/efi/boot/
profile=$(basename $2)
if [[ $profile =~ ^sle.* ]]; then
sed -i 's/%%IFSLE%%//;s/%%ENDIFSLE%%//' $2/autoyast
else
sed -i 's/%%IFSLE%%/<!--/;s/%%ENDIFSLE%%/-->/' $2/autoyast
fi
@@ -0,0 +1,2 @@
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet
@@ -0,0 +1,10 @@
#!/bin/sh
# This script runs at the end of the final boot, updating status
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //')
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
curl --capath /etc/confluent/tls -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -f -X POST -d "status: complete" https://$mgr/confluent-api/self/updatestatus
@@ -0,0 +1,88 @@
import subprocess
import os
class DiskInfo(object):
def __init__(self, devname):
self.name = devname
self.wwn = None
self.path = None
self.model = ''
self.size = 0
self.driver = None
self.mdcontainer = ''
devnode = '/dev/{0}'.format(devname)
qprop = subprocess.check_output(
['udevadm', 'info', '--query=property', devnode])
if not isinstance(qprop, str):
qprop = qprop.decode('utf8')
for prop in qprop.split('\n'):
if '=' not in prop:
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
self.path = v
elif k == 'ID_WWN':
self.wwn = v
elif k == 'MD_CONTAINER':
self.mdcontainer = v
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
if not isinstance(attrs, str):
attrs = attrs.decode('utf8')
for attr in attrs.split('\n'):
if '==' not in attr:
continue
k, v = attr.split('==', 1)
k = k.strip()
if k == 'ATTRS{size}':
self.size = v.replace('"', '')
elif (k == 'DRIVERS' and not self.driver
and v not in ('"sd"', '""')):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2'):
return 0
if 'imsm' in self.mdcontainer:
return 1
if self.driver == 'ahci':
return 2
if self.driver.startswith('megaraid'):
return 3
if self.driver.startswith('mpt'):
return 4
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def main():
disks = []
for disk in sorted(os.listdir('/sys/class/block')):
try:
disk = DiskInfo(disk)
disks.append(disk)
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
if __name__ == '__main__':
main()
@@ -0,0 +1,49 @@
#!/bin/sh
# This script runs when install is finished, but while the installer
# is still running, with the to-be-booted system mounted in /mnt
# Carry over install-time ssh material into installed system
mkdir -p /mnt/root/.ssh/
chmod 700 /mnt/root/.ssh/
cp /root/.ssh/authorized_keys /mnt/root/.ssh/
chmd 600 /mnt/root/.ssh/authorized_keys
cp /etc/ssh/*key* /mnt/etc/ssh/
for i in /etc/ssh/*-cert.pub; do
echo HostCertificate $i >> /mnt/etc/ssh/sshd_config
done
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /mnt/etc/ssh/ssh_known_hosts
done
# Enable ~/.shosts, for the sake of root user, who is forbidden from using shosts.equiv
echo IgnoreRhosts no >> /mnt/etc/ssh/sshd_config
echo HostbasedAuthentication yes >> /mnt/etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /mnt/etc/ssh/sshd_config
echo Host '*' >> /mnt/etc/ssh/ssh_config
echo " HostbasedAuthentication yes" >> /mnt/etc/ssh/ssh_config
echo " EnableSSHKeysign yes" >> /mnt/etc/ssh/ssh_config
# Limit the attempts of using host key. This prevents client from using 3 or 4
# authentication attempts through host based attempts
echo " HostbasedKeyTypes *ed25519*" >> /mnt/etc/ssh/ssh_config
# In SUSE platform, setuid for ssh-keysign is required for host based,
# and also must be opted into.
echo /usr/lib/ssh/ssh-keysign root:root 4711 >> /mnt/etc/permissions.local
chmod 4711 /mnt/usr/lib/ssh/ssh-keysign
# Download list of nodes from confluent, and put it into shosts.equiv (for most users) and .shosts (for root)
nodename=$(grep ^NODENAME /tmp/confluent.info|awk '{print $2}')
curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /tmp/confluent.apikey)" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
cp /tmp/allnodes /mnt/root/.shosts
cp /tmp/allnodes /mnt/etc/ssh/shosts.equiv
# carry over deployment configuration and api key for OS install action
mkdir -p /mnt/etc/confluent
chmod 700 /mnt/etc/confluent
chmod 600 /tmp/confluent.*
cp /tmp/confluent.* /mnt/etc/confluent/
cp -a /tls /mnt/etc/confluent/
cp -a /tls/* /mnt/var/lib/ca-certificates/openssl
cp -a /tls/* /mnt/var/lib/ca-certificates/pem
cp -a /tls/*.pem /mnt/etc/pki/trust/anchors
@@ -0,0 +1,24 @@
#!/bin/sh
# This script runs before the installer executes, and sets up ssh during install as well
# as rewriting the autoyast file with any substitutions prior to it being evaluated for real
nodename=$(grep ^NODENAME /tmp/confluent.info|awk '{print $2}')
rootpw=$(grep rootpassword: /tmp/confluent.deploycfg|sed -e 's/^rootpassword: //')
if [ "$rootpw" = "null" ]; then
rootpw="!"
fi
mkdir ~/.ssh
cat /ssh/*.rootpubkey > ~/.ssh/authorized_keys
ssh-keygen -A
for i in /etc/ssh/ssh_host*key.pub; do
certname=${i/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /tmp/confluent.apikey)" -d @$i https://$mgr/confluent-api/self/sshcert > $certname
echo HostKey ${i%.pub} >> /etc/ssh/sshd_config
echo HostCertificate $certname >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
curl -f https://$mgr/confluent-public/os/$profile/scripts/getinstalldisk > /tmp/getinstalldisk
python3 /tmp/getinstalldisk
sed -e s!%%INSTDISK%%!/dev/$(cat /tmp/installdisk)! -e s!%%NODENAME%%!$nodename! -e "s?%%ROOTPASSWORD%%?${rootpw}?" /tmp/profile/autoinst.xml > /tmp/profile/modified.xml
@@ -0,0 +1,8 @@
if ! grep console= /proc/cmdline > /dev/null; then
/opt/confluent/bin/autocons > /custom-installation/autocons.info
cons=$(cat /custom-installation/autocons.info)
if [ ! -z "$cons" ]; then
echo "Auto-detected serial console: $cons" > ${cons%,*}
fi
fi
echo /scripts/init-premount/confluent >> /scripts/init-premount/ORDER
@@ -0,0 +1,67 @@
cd /root
cat /tls/*.0 > /root/etc/ssl/certs/ca-certificates.crt
mkdir -p /root/custom-installation/ssh
mkdir -p /root/custom-installation/tls
cp /ssh/* /root/custom-installation/ssh
cp /tls/* /root/custom-installation/tls
NODENAME=$(grep ^NODENAME: /custom-installation/confluent/confluent.info|awk '{print $2}')
MGR=$(grep ^MANAGER: /custom-installation/confluent/confluent.info|head -n 1| awk '{print $2}')
oum=$(umask)
umask 077
chroot . custom-installation/confluent/bin/clortho $NODENAME $MGR > /root/custom-installation/confluent/confluent.apikey
MGR=[$MGR]
deploycfg=/root/custom-installation/confluent/confluent.deploycfg
chroot . usr/bin/curl -f -H "CONFLUENT_NODENAME: $NODENAME" -H "CONFLUENT_APIKEY: $(cat /root//custom-installation/confluent/confluent.apikey)" https://${MGR}/confluent-api/self/deploycfg > $deploycfg
umask $oum
nic=$(grep ^MANAGER /custom-installation/confluent/confluent.info|grep fe80::|sed -e s/.*%//|head -n 1)
nic=$(ip link |grep ^$nic:|awk '{print $2}')
DEVICE=${nic%:}
ipv4m=$(grep ^ipv4_method $deploycfg|awk '{print$2}')
. /scripts/functions
if [ "$ipv4m" = "dhcp" ]; then
IP=dhcp
configure_networking
elif [ "$ipv4m" = "static" ]; then
v4addr=$(grep ^ipv4_address: $deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: $deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ipv4_netmask: $deploycfg)
v4nm=${v4nm#ipv4_netmask: }
dnsdomain=$(grep ^dnsdomain: $deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
if [ "$dnsdomain" = "null" ]; then dnsdomain=""; fi
dns=$(grep -A1 ^nameservers: $deploycfg|head -n 2|tail -n 1|sed -e 's/^- //'|sed -e "s/''//")
{
echo "DEVICE='$DEVICE'"
echo "PROTO='none'"
echo "IPV4PROTO='none'"
echo "IPV4ADDR='$v4addr'"
echo "IPV4NETMASK='$v4nm'"
echo "IPV4BROADCAST='$v4nm'"
echo "IPV4GATEWAY='$v4gw'"
echo "IPV4DNS1='$dns'"
echo "HOSTNAME='$NODENAME'"
echo "DNSDOMAIN='$dnsdomain'"
echo "DOMAINSEARCH='$dnsdomain'"
} > "/run/net-$DEVICE.conf"
configure_networking
else
IP=off
fi
ipv4s=$(grep ^deploy_server $deploycfg|awk '{print $2}')
osprofile=$(cat /custom-installation/confluent/osprofile)
fcmdline="$(cat /custom-installation/confluent/cmdline.orig) autoinstall ds=nocloud-net;s=https://${ipv4s}/confluent-public/os/${osprofile}/autoinstall/"
if [ -f /custom-installation/autocons.info ]; then
cons=$(cat /custom-installation/autocons.info)
fi
if [ ! -z "$cons" ]; then
echo "Installation will proceed on graphics console, autoconsole not supported during autoinstall phase" > ${cons%,*}
echo "Progress can be checked by using ssh to access and running the screendump command" > ${cons%,*}
#fcmdline="$fcmdline console=${cons#/dev/}"
fi
echo $fcmdline > /custom-installation/confluent/fakecmdline
/scripts/casper-bottom/58server_network
@@ -0,0 +1,6 @@
#!/bin/bash
deploycfg=/custom-installation/confluent/confluent.deploycfg
mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
profile=$(grep ^profile: $deploycfg|awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/post.sh > /tmp/post.sh
. /tmp/post.sh
@@ -0,0 +1,6 @@
#!/bin/bash
deploycfg=/custom-installation/confluent/confluent.deploycfg
mgr=$(grep ^deploy_server $deploycfg|awk '{print $2}')
profile=$(grep ^profile: $deploycfg|awk '{print $2}')
curl -f https://$mgr/confluent-public/os/$profile/scripts/pre.sh > /tmp/pre.sh
. /tmp/pre.sh
@@ -0,0 +1,30 @@
cd /sys/class/net
for nic in *; do
ip link set $nic up
done
mkdir -p /custom-installation
cp -a /opt/confluent /custom-installation
touch /custom-installation/confluent/confluent.info
while ! grep NODENAME /custom-installation/confluent/confluent.info; do
/opt/confluent/bin/copernicus -t > /custom-installation/confluent/confluent.info
done
MGR="[$(grep MANAGER: /custom-installation/confluent/confluent.info | head -n 1 | awk '{print $2}')]"
osprofile=$(sed -e 's/.*osprofile=//' -e 's/ .*//' /proc/cmdline)
cat /proc/cmdline > /custom-installation/confluent/cmdline.orig
if [ -f /custom-installation/autocons.info ]; then
cons=$(cat /custom-installation/autocons.info)
fi
if [ ! -z "$cons" ]; then
echo "Preparing to deploy $osprofile from $MGR" > ${cons%,*}
fi
echo "Preparing to deploy $osprofile from $MGR"
echo $osprofile > /custom-installation/confluent/osprofile
echo URL=http://${MGR}/confluent-public/os/$osprofile/distribution/install.iso >> /conf/param.conf
fcmdline="$(cat /custom-installation/confluent/cmdline.orig) url=http://${MGR}/confluent-public/os/$osprofile/distribution/install.iso"
if [ ! -z "$cons" ]; then
fcmdline="$fcmdline console=${cons#/dev/}"
fi
echo $fcmdline > /custom-installation/confluent/fakecmdline
mount -o bind /custom-installation/confluent/fakecmdline /proc/cmdline
@@ -0,0 +1,17 @@
#cloud-config
autoinstall:
version: 1
early-commands:
- /custom-installation/pre.sh
late-commands:
- /custom-installation/post.sh
ssh:
install-server: true
storage:
layout:
name: lvm
match:
path: "%%INSTALLDISK%%"
user-data:
runcmd:
- /etc/confluent/firstboot.sh
@@ -0,0 +1,7 @@
#!/bin/sh
sed -i 's/label: ubuntu/label: Ubuntu/' $2/profile.yaml && \
ln -s $1/casper/vmlinuz $2/boot/kernel && \
ln -s $1/casper/initrd $2/boot/initramfs/distribution && \
mkdir -p $2/boot/efi/boot && \
ln -s $1/EFI/BOOT/* $2/boot/efi/boot
@@ -0,0 +1,2 @@
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet osprofile=%%PROFILE%%
@@ -0,0 +1,14 @@
#!/bin/bash
echo "Confluent first boot is running"
cp -a /etc/confluent/ssh/* /etc/ssh/
systemctl restart sshd
rootpw=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ ! -z "$rootpw" -a "$rootpw" != "null" ]; then
echo root:$rootpw | chpasswd -e
fi
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
apikey=$(cat /etc/confluent/confluent.apikey)
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}')
hostnamectl set-hostname $(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
touch /etc/cloud/cloud-init.disabled
curl --capath /etc/confluent/tls -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -X POST -d "status: complete" https://$mgr/confluent-api/self/updatestatus
@@ -0,0 +1,88 @@
import subprocess
import os
class DiskInfo(object):
def __init__(self, devname):
self.name = devname
self.wwn = None
self.path = None
self.model = ''
self.size = 0
self.driver = None
self.mdcontainer = ''
devnode = '/dev/{0}'.format(devname)
qprop = subprocess.check_output(
['udevadm', 'info', '--query=property', devnode])
if not isinstance(qprop, str):
qprop = qprop.decode('utf8')
for prop in qprop.split('\n'):
if '=' not in prop:
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
self.path = v
elif k == 'ID_WWN':
self.wwn = v
elif k == 'MD_CONTAINER':
self.mdcontainer = v
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
if not isinstance(attrs, str):
attrs = attrs.decode('utf8')
for attr in attrs.split('\n'):
if '==' not in attr:
continue
k, v = attr.split('==', 1)
k = k.strip()
if k == 'ATTRS{size}':
self.size = v.replace('"', '')
elif (k == 'DRIVERS' and not self.driver
and v not in ('"sd"', '""')):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2'):
return 0
if 'imsm' in self.mdcontainer:
return 1
if self.driver == 'ahci':
return 2
if self.driver.startswith('megaraid'):
return 3
if self.driver.startswith('mpt'):
return 4
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def main():
disks = []
for disk in sorted(os.listdir('/sys/class/block')):
try:
disk = DiskInfo(disk)
disks.append(disk)
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
if __name__ == '__main__':
main()
@@ -0,0 +1,42 @@
#!/bin/bash
cp -a /root/.ssh /target/root/
mkdir -p /target/etc/confluent/ssh/sshd_config.d/
chmod 700 /target/etc/confluent
cp /custom-installation/confluent/* /target/etc/confluent/
cp -a /custom-installation/tls /target/etc/confluent/
chmod go-rwx /etc/confluent/*
for i in /custom-installation/ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /target/etc/ssh/ssh_known_hosts
done
cp -a /etc/ssh/ssh_host* /target/etc/confluent/ssh/
cp -a /etc/ssh/sshd_config.d/confluent.conf /target/etc/confluent/ssh/sshd_config.d/
sshconf=/target/etc/ssh/ssh_config
if [ -d /target/etc/ssh/ssh_config.d/ ]; then
sshconf=/target/etc/ssh/ssh_config.d/01-confluent.conf
fi
echo 'Host *' >> $sshconf
echo ' HostbasedAuthentication yes' >> $sshconf
echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /target/etc/confluent/firstboot.sh
chmod +x /target/etc/confluent/firstboot.sh
cp /tmp/allnodes /target/root/.shosts
cp /tmp/allnodes /target/etc/ssh/shosts.equiv
textcons=$(grep ^textconsole: /target/etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$textcons" = "true" ] && ! grep console= /proc/cmdline > /dev/null; then
cons=""
if [ -f /custom-installation/autocons.info ]; then
cons=$(cat /custom-installation/autocons.info)
fi
if [ ! -z "$cons" ]; then
sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 console='${cons#/dev/}'"/' /target/etc/default/grub
mount -o bind /dev /target/dev
mount -o bind /proc /target/proc
mount -o bind /sys /target/sys
chroot /target update-grub
umount /target/sys /target/dev /target/proc
fi
fi
@@ -0,0 +1,19 @@
#!/bin/bash
cat /custom-installation/ssh/*.rootpubkey > /root/.ssh/authorized_keys
nodename=$(grep ^NODENAME: /custom-installation/confluent/confluent.info|awk '{print $2}')
apikey=$(cat /custom-installation/confluent/confluent.apikey)
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
keyfile=${pubkey%.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -d @$pubkey https://$mgr/confluent-api/self/sshcert > $certfile
echo HostKey $keyfile >> /etc/ssh/sshd_config.d/confluent.conf
echo HostCertificate $certfile >> /etc/ssh/sshd_config.d/confluent.conf
done
echo HostbasedAuthentication yes >> /etc/ssh/sshd_config.d/confluent.conf
echo HostbasedUsesNameFromPacketOnly yes >> /etc/ssh/sshd_config.d/confluent.conf
echo IgnoreRhosts no >> /etc/ssh/sshd_config.d/confluent.conf
systemctl restart sshd
curl -f X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/nodelist > /tmp/allnodes
curl -f https://$mgr/confluent-public/os/$profile/scripts/getinstalldisk > /custom-installation/getinstalldisk
python3 /custom-installation/getinstalldisk
sed -i s!%%INSTALLDISK%%!/dev/$(cat /tmp/installdisk)! /autoinstall.yaml
+16
View File
@@ -0,0 +1,16 @@
CC := gcc
CFLAGS := -Os
TARGETS := copernicus autocons
all: $(TARGETS) clortho
clortho: clortho.c
$(CC) $(CFLAGS) -o $@ $^ -lcrypt
strip -s $@
$(TARGETS): % : %.c
$(CC) $(CFLAGS) -o $@ $^
strip -s $@
clean:
rm $(TARGETS) clortho
@@ -25,6 +25,7 @@ int main(int argc, char* argv[]) {
int currspeed;
speed_t cspeed;
char buff[128];
char* offset;
uint64_t address;
spcr = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
if (spcr < 0) {
@@ -38,6 +39,7 @@ int main(int argc, char* argv[]) {
if (buff[40] != 1) exit(0); //IO only
address = *(uint64_t *)(buff + 44);
currspeed = buff[58];
offset = buff + 10;
if (address == COM1) {
strncpy(buff, "/dev/ttyS0", 128);
} else if (address == COM2) {
@@ -51,14 +53,16 @@ int main(int argc, char* argv[]) {
}
if (currspeed == SPEED9600) {
cspeed = B9600;
strcpy(offset, ",9600");
} else if (currspeed == SPEED19200) {
cspeed = B19200;
strcpy(offset, ",19200");
} else if (currspeed == SPEED57600) {
cspeed = B57600;
strcpy(offset, ",57600");
} else if (currspeed == SPEED115200) {
cspeed = B115200;
} else if (currspeed == SPEED115200) {
cspeed = 0;
strcpy(offset, ",115200");
} else {
exit(0);
}
+130
View File
@@ -0,0 +1,130 @@
/* Copyright 2019 Lenovo */
#include <arpa/inet.h>
#include <crypt.h>
#include <net/if.h>
#include <netdb.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define MAXPACKET 1024
static const char cryptalpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./";
unsigned char* genpasswd(int len) {
unsigned char * passwd;
int urandom, ret;
passwd = calloc(len + 1, sizeof(char));
urandom = open("/dev/urandom", O_RDONLY);
ret = read(urandom, passwd, len);
close(urandom);
for (urandom = 0; urandom < len; urandom++) {
passwd[urandom] = cryptalpha[passwd[urandom] >> 2];
}
return passwd;
}
int main(int argc, char* argv[]) {
int sock, ret;
char slen;
unsigned char currlen, currtype;
unsigned char* passwd;
unsigned char* cryptedpass;
unsigned char* macaddr;
struct timeval timeout;
struct addrinfo hints;
struct addrinfo *addrs;
struct addrinfo *curr;
struct sockaddr_in net4bind;
struct sockaddr_in6 net6bind;
unsigned char buffer[MAXPACKET];
memset(&hints, 0, sizeof(struct addrinfo));
memset(&net4bind, 0, sizeof(struct sockaddr_in));
memset(&net6bind, 0, sizeof(struct sockaddr_in6));
memset(&buffer, 0, MAXPACKET);
memset(&timeout, 0, sizeof(struct timeval));
timeout.tv_sec = 10;
net4bind.sin_port = htons(302);
net6bind.sin6_port = htons(302);
net6bind.sin6_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
passwd = genpasswd(32);
memset(buffer, 0, MAXPACKET);
strncpy(buffer, "$5$", 3);
cryptedpass = genpasswd(8);
strncpy(buffer + 3, cryptedpass, 8);
free(cryptedpass);
cryptedpass = crypt(passwd, buffer);
if (argc < 3) {
fprintf(stderr, "Missing node name and manager\n");
exit(1);
}
sock = getaddrinfo(argv[2], "13001", &hints, &addrs);
if (sock != 0) {
fprintf(stderr, "Error trying to resolve %s\n", argv[2]);
exit(1);
}
for (curr = addrs; curr != NULL; curr = curr->ai_next) {
sock = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (sock < 0) continue;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
if (curr->ai_family == AF_INET) {
bind(sock, (struct sockaddr*)&net4bind, sizeof(struct sockaddr_in));
} else if (curr->ai_family == AF_INET6) {
bind(sock, (struct sockaddr*)&net6bind, sizeof(struct sockaddr_in6));
} else {
continue;
}
if (connect(sock, curr->ai_addr, curr->ai_addrlen) == 0) break;
}
if (curr == NULL) {
fprintf(stderr, "Unable to reach %s\n", argv[2]);
exit(1);
}
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
freeaddrinfo(addrs);
ret = read(sock, buffer, 8);
if (memcmp(buffer, "\xc2\xd1-\xa8\x80\xd8j\xba", 8) != 0) {
fprintf(stderr, "Unrecognized server\n");
exit(1);
}
slen = strlen(argv[1]) & 0xff;
dprintf(sock, "\x01%c%s", slen, argv[1]);
ret = write(sock, "\x00\x00", 2);
memset(buffer, 0, MAXPACKET);
ret = read(sock, buffer, 2);
while (buffer[0] != 255) {
currtype = buffer[0];
currlen = buffer[1];
memset(buffer, 0, MAXPACKET);
if (currlen) {
ret = read(sock, buffer, currlen); // Max is 255, well under MAX_PACKET
}
if (currtype == 2) {
dprintf(sock, "\x03%c", currlen);
ret = write(sock, buffer, currlen);
slen = strlen(cryptedpass) & 0xff;
dprintf(sock, "\x04%c%s", slen, cryptedpass);
ret = write(sock, "\x00\x00", 2);
} else if (currtype == 5) {
printf("%s", passwd);
printf("\n");
exit(0);
}
buffer[0] = 255;
ret = read(sock, buffer, 2);
}
fprintf(stderr, "Password was not accepted\n");
exit(1);
}
@@ -48,8 +48,7 @@ int add_macs(char* destination, int maxsize) {
} else if (lla->sll_hatype == ARPHRD_ETHER) {
snprintf(macaddr, 32, "/mac=%02x:%02x:%02x:%02x:%02x:%02x",
lla->sll_addr[0], lla->sll_addr[1], lla->sll_addr[2],
lla->sll_addr[3], lla->sll_addr[4], lla->sll_addr[5],
lla->sll_addr[6]
lla->sll_addr[3], lla->sll_addr[4], lla->sll_addr[5]
);
} else {
continue;
@@ -68,10 +67,16 @@ int main(int argc, char* argv[]) {
struct sockaddr_in6 addr, dst;
struct sockaddr_in addr4, dst4;
char msg[1024];
char *nodenameidx;
char nodename[1024];
char lastnodename[1024];
char lastmsg[1024];
char last6msg[1024];
int ifidx, offset;
fd_set rfds;
struct timeval tv;
int settime = 0;
int setusec = 500000;
socklen_t dstsize, dst4size;
dstsize = sizeof(dst);
dst4size = sizeof(dst4);
@@ -80,6 +85,10 @@ int main(int argc, char* argv[]) {
memset(&addr, 0, sizeof(addr));
memset(&dst, 0, sizeof(dst));
memset(&dst4, 0, sizeof(dst4));
memset(nodename, 0, 1024);
memset(lastnodename, 0, 1024);
memset(lastmsg, 0, 1024);
memset(last6msg, 0, 1024);
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = htons(190);
@@ -94,10 +103,6 @@ int main(int argc, char* argv[]) {
inet_pton(AF_INET, "239.255.255.250", &dst4.sin_addr);
strncpy(msg, "M-SEARCH * HTTP/1.1\r\nST: urn:xcat.org:service:confluent:", 1024);
offset = strnlen(msg, 1024);
if (argc > 1) {
snprintf(msg + offset, 1024 - offset, "/node=%s", argv[1]);
offset = strnlen(msg, 1024);
}
add_uuid(msg + offset, 1024 - offset);
offset = strnlen(msg, 1024);
add_macs(msg + offset, 1024 - offset);
@@ -134,30 +139,100 @@ int main(int argc, char* argv[]) {
FD_ZERO(&rfds);
FD_SET(n4, &rfds);
FD_SET(ns, &rfds);
tv.tv_sec = 10;
tv.tv_usec = 0;
tv.tv_sec = 2;
tv.tv_usec = 500000;
ifidx = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
while (ifidx) {
if (ifidx == -1) perror("Unable to select");
if (ifidx) {
if (FD_ISSET(n4, &rfds)) {
recvfrom(n4, msg, 1024, 0, (struct sockaddr *)&dst4, &dst4size);
memset(msg, 0, 1024);
/* Deny packet access to the last 24 bytes to assure null */
recvfrom(n4, msg, 1000, 0, (struct sockaddr *)&dst4, &dst4size);
if (nodenameidx = strstr(msg, "NODENAME: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
nodenameidx = strstr(nodename, "\r");
if (nodenameidx) { nodenameidx[0] = 0; }
if (strncmp(lastnodename, nodename, 1024) != 0) {
printf("NODENAME: %s\n", nodename);
strncpy(lastnodename, nodename, 1024);
}
}
if (nodenameidx = strstr(msg, "CURRTIME: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
if (nodenameidx = strstr(nodename, "\r")) {
nodenameidx[0] = 0;
}
settime = strtol(nodename, NULL, 10);
}
if (nodenameidx = strstr(msg, "CURRMSECS: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
if (nodenameidx = strstr(nodename, "\r")) {
nodenameidx[0] = 0;
}
setusec = strtol(nodename, NULL, 10) * 1000;
}
memset(msg, 0, 1024);
inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size);
/* Take measure from printing out the same ip twice in a row */
if (strncmp(lastmsg, msg, 1024) != 0) {
printf("%s\n", msg);
sendto(n4, "PING", 4, 0, (const struct sockaddr *)&dst4, dst4size);
printf("MANAGER: %s\n", msg);
strncpy(lastmsg, msg, 1024);
}
}
if (FD_ISSET(ns, &rfds)) {
recvfrom(ns, msg, 1024, 0, (struct sockaddr *)&dst, &dstsize);
memset(msg, 0, 1024);
/* Deny packet access to the last 24 bytes to assure null */
recvfrom(ns, msg, 1000, 0, (struct sockaddr *)&dst, &dstsize);
if (nodenameidx = strstr(msg, "NODENAME: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
nodenameidx = strstr(nodename, "\r");
if (nodenameidx) { nodenameidx[0] = 0; }
if (strncmp(lastnodename, nodename, 1024) != 0) {
printf("NODENAME: %s\n", nodename);
strncpy(lastnodename, nodename, 1024);
}
}
if (nodenameidx = strstr(msg, "CURRTIME: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
if (nodenameidx = strstr(nodename, "\r")) {
nodenameidx[0] = 0;
}
settime = strtol(nodename, NULL, 10);
}
if (nodenameidx = strstr(msg, "CURRMSECS: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
if (nodenameidx = strstr(nodename, "\r")) {
nodenameidx[0] = 0;
}
setusec = strtol(nodename, NULL, 10) * 1000;
}
memset(msg, 0, 1024);
inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize);
if (strncmp(lastmsg, msg, 1024) != 0) {
printf("%s\n", msg);
strncpy(lastmsg, msg, 1024);
if (strncmp(last6msg, msg, 1024) != 0) {
sendto(ns, "PING", 4, 0, (const struct sockaddr *)&dst, dstsize);
printf("MANAGER: %s", msg);
if (strncmp(msg, "fe80::", 6) == 0) {
printf("%%%u", dst.sin6_scope_id);
}
printf("\n");
strncpy(last6msg, msg, 1024);
}
}
}
if (settime && argc > 1 && strcmp(argv[1], "-t") == 0) {
tv.tv_sec = settime;
tv.tv_usec = setusec;
settimeofday(&tv, NULL);
settime = 0;
}
tv.tv_sec = 0;
tv.tv_usec = 500000;
FD_SET(n4, &rfds);
-55
View File
@@ -1,55 +0,0 @@
from os.path import exists
import shutil
import socket
import subprocess
import tempfile
def get_openssl_conf_location():
if exists('/etc/pki/tls/openssl.cnf'):
return '/etc/pki/tls/openssl.cnf'
elif exists('/etc/ssl/openssl.cnf'):
return '/etc/ssl/openssl.cnf'
else:
raise Exception("Cannot find openssl config file")
def get_ip_addresses():
lines = subprocess.check_output('ip addr'.split(' '))
for line in lines.split('\n'):
if line.startswith(' inet6 '):
line = line.replace(' inet6 ', '').split('/')[0]
if line.startswith('fe80::'):
continue
if line == '::1':
continue
elif line.startswith(' inet '):
line = line.replace(' inet ', '').split('/')[0]
if line == '127.0.0.1':
continue
if line.startswith('169.254.'):
continue
else:
continue
yield line
def create_certificate():
shortname = socket.gethostname().split('.')[0]
longname = socket.getfqdn()
subprocess.check_call(
'openssl ecparam -name secp384r1 -genkey -out privkey.pem'.split(' '))
san = ['IP:{0}'.format(x) for x in get_ip_addresses()]
san.append('DNS:{0}'.format(shortname))
san.append('DNS:{0}'.format(longname))
san = ','.join(san)
sslcfg = get_openssl_conf_location()
tmpconfig = tempfile.mktemp()
shutil.copy2(sslcfg, tmpconfig)
with open(tmpconfig, 'a') as cfgfile:
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
subprocess.check_call(
'openssl req -new -x509 -key privkey.pem -days 7300 -out cert.pem '
'-subj /CN={0} -extensions SAN '
'-config {1}'.format(longname, tmpconfig).split(' ')
)
if __name__ == '__main__':
create_certificate()
+5 -2
View File
@@ -62,8 +62,11 @@ if args[0] == 'restore':
if pid is not None:
print("Confluent is running, must shut down to restore db")
sys.exit(1)
password = options.password
if options.interactivepassword:
password = getpass.getpass('Enter password to restore backup: ')
try:
cfm.restore_db_from_directory(dumpdir, options.password)
cfm.restore_db_from_directory(dumpdir, password)
except Exception as e:
print(str(e))
sys.exit(1)
@@ -86,7 +89,7 @@ elif args[0] == 'dump':
main._initsecurity(conf.get_config())
if not os.path.exists(dumpdir):
os.makedirs(dumpdir)
cfm.dump_db_to_directory(dumpdir, options.password, options.redact,
cfm.dump_db_to_directory(dumpdir, password, options.redact,
options.skipkeys)
+186
View File
@@ -0,0 +1,186 @@
#!/usr/bin/python2
import argparse
import eventlet.green.subprocess as subprocess
import glob
import os
import os.path
import shutil
import sys
import time
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sshutil as sshutil
import confluent.certutil as certutil
try:
input = raw_input
except NameError:
pass
fnamechars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.^'
def main(args):
ap = argparse.ArgumentParser(description='Manage OS deployment resources')
sp = ap.add_subparsers(dest='command')
wiz = sp.add_parser('initialize', help='Do OS deployment preparation')
wiz.add_argument('-u', help='Pull in root user key for node deployment', action='store_true')
wiz.add_argument('-s', help='Set up SSH CA for managing node to node ssh and known hosts', action='store_true')
wiz.add_argument('-k', help='Update local global known hosts file with confluent CA', action='store_true')
wiz.add_argument('-t', help='Generate new TLS key for HTTPS operation and register with confluent repository', action='store_true')
wiz.add_argument('-p', help='Copy in TFTP contents required for PXE support', action='store_true')
wiz.add_argument('-i', help='Interactively prompt for behaviors', action='store_true')
osip = sp.add_parser('import', help='Import an OS image from an ISO image')
osip.add_argument('imagefile', help='File to use for source of importing')
cmdset = ap.parse_args()
if cmdset.command == 'import':
return osimport(cmdset.imagefile)
if cmdset.command == 'initialize':
return initialize(cmdset)
ap.print_help()
def install_tftp_content():
tftplocation = None
candidates = ('/var/lib/tftpboot', '/srv/tftpboot', '/srv/tftp')
for cand in candidates:
if os.path.isdir(cand):
tftplocation = cand
break
if not tftplocation:
raise Exception('Unable to detect an installed tftp location')
tftplocation = '{0}/confluent/x86_64'.format(tftplocation)
try:
os.makedirs(tftplocation)
except OSError as e:
if e.errno == 17:
raise
shutil.copy('/opt/confluent/lib/ipxe/ipxe.efi', tftplocation)
shutil.copy('/opt/confluent/lib/ipxe/ipxe.kkpxe', tftplocation)
def initialize(cmdset):
if os.getuid() != 0:
sys.stderr.write('This command must run as root user\n')
sys.exit(1)
if cmdset.i:
didsomething = True
sys.stdout.write('Add root user key to be authorized to log into nodes (-u)? (y/n): ')
sys.stdout.flush()
cmdset.u = input().strip().lower().startswith('y')
sys.stdout.write('Set up an SSH authority to help manage known_hosts and node to node ssh for all users (-s)? (y/n): ')
cmdset.s = input().strip().lower().startswith('y')
sys.stdout.write('Update global known hosts on this server to trust local CA certificates (-k)? (y/n): ')
cmdset.k = input().strip().lower().startswith('y')
sys.stdout.write('Update tftp directory with binaries to support PXE (-p) (y/n): ')
cmdset.p = input().strip().lower().startswith('y')
sys.stdout.write('Generate new TLS certificates for HTTP, replacing any existing certificate (-t)? (y/n): ')
cmdset.t = input().strip().lower().startswith('y')
if not cmdset.t:
print(
'In order to use your own certificate authority, make sure '
'to put the certificate authority into '
'/var/lib/confluent/public/site/tls/ directory as a .pem file '
'as well as named (hash).0 where (hash) is the hash of the '
'subject.')
else:
didsomething = False
if cmdset.u:
if not glob.glob('/root/.ssh/*.pub'):
didsomething = True
sys.stderr.write('No user keys for root detected, it is recommended '
'to run ssh-keygen -t ed25519 to generate a user '
'key. For optimal security, a passphrase should be '
'used. ssh-agent may be used to make use of a '
'passphrase protected ssh key easier.\n')
sys.exit(1)
sshutil.initialize_root_key(False)
if cmdset.t:
didsomething = True
certutil.create_certificate()
print('New HTTPS certificates generated, restart the web server')
if cmdset.s:
didsomething = True
sshutil.initialize_ca()
if not didsomething:
sys.stderr.write('Nothing was done, use initialize -i for '
'interactive mode, or see initialize -h for more options\n')
sys.exit(1)
tmpname = '/var/lib/confluent/public/site/initramfs.cpio.'
for x in bytearray(os.urandom(22)):
tmpname += fnamechars[x >> 2]
topack = []
opath = os.getcwd()
os.chdir('/var/lib/confluent/public/site')
topack.append('ssh/')
for currd, _, files in os.walk('ssh'):
for fname in files:
topack.append(os.path.join(currd, fname))
topack.append('tls/')
for currd, _, files in os.walk('tls'):
for fname in files:
topack.append(os.path.join(currd, fname))
with open(tmpname, 'wb') as initramfs:
packit = subprocess.Popen(['cpio', '-H', 'newc', '-o'],
stdout=initramfs, stdin=subprocess.PIPE)
for packfile in topack:
if not isinstance(packfile, bytes):
packfile = packfile.encode('utf8')
packit.stdin.write(packfile)
packit.stdin.write(b'\n')
packit.stdin.close()
res = packit.wait()
if res:
sys.stderr.write('Error occurred while packing site initramfs')
sys.exit(1)
os.chdir(opath)
os.rename(tmpname, '/var/lib/confluent/public/site/initramfs.cpio')
if cmdset.k:
with open('/etc/ssh/ssh_known_hosts', 'a+b') as skh:
for cafile in glob.glob('/var/lib/confluent/public/site/ssh/*.ca'):
cacert = open(cafile, 'rb').read()
cacert = b'@cert-authority * ' + cacert
skh.write(cacert)
if cmdset.p:
install_tftp_content()
# ok, also need to think on how to handle getinstalldisk
if not os.path.exists('/etc/confluent/srvcert.pem'):
subprocess.check_call(['collective', 'gencert'])
# TODO: check selinux and segetbool for httpd_can_network_connect
# tftp-server available and enabled?
# httpd available and enabled?
def osimport(imagefile):
c = client.Command()
imagefile = os.path.abspath(imagefile)
importing = False
shortname = None
for rsp in c.create('/deployment/importing/', {'filename': imagefile}):
if 'target' in rsp:
importing = True
shortname = rsp['name']
print('Importing from {0} to {1}'.format(imagefile, rsp['target']))
else:
print(repr(rsp))
while importing:
for rsp in c.read('/deployment/importing/{0}'.format(shortname)):
if 'progress' in rsp:
sys.stdout.write('{0}: {1:.2f}% \r'.format(rsp['phase'],
rsp['progress']))
if rsp['phase'] == 'complete':
importing = False
sys.stdout.write('\n')
for profile in rsp['profiles']:
print('Deployment profile created: {0}'.format(profile))
sys.stdout.flush()
else:
print(repr(rsp))
time.sleep(0.5)
list(c.delete('/deployment/importing/{0}'.format(shortname)))
if __name__ == '__main__':
main(sys.argv)
+4 -1
View File
@@ -36,7 +36,7 @@ if [ "$OPKGNAME" = "confluent-server" ]; then
if grep wheezy /etc/os-release; then
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl, python-msgpack/' debian/control
else
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack/' debian/control
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-eventlet, python3-pyparsing, python3-pyte, python3-pyghmi, python3-paramiko/' debian/control
fi
if grep wheezy /etc/os-release; then
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
@@ -44,6 +44,9 @@ if [ "$OPKGNAME" = "confluent-server" ]; then
echo 'confluent_client confluent-client' >> debian/pydist-overrides
fi
fi
if ! grep wheezy /etc/os-release; then
sed -i 's/^Package: python3-/Package: /' debian/control
fi
head -n -1 debian/control > debian/control1
mv debian/control1 debian/control
echo 'export PYBUILD_INSTALL_ARGS=--install-lib=/opt/confluent/lib/python' >> debian/rules
+130
View File
@@ -0,0 +1,130 @@
import os
import confluent.collective.manager as collective
from os.path import exists
import shutil
import socket
import eventlet.green.subprocess as subprocess
import tempfile
def get_openssl_conf_location():
if exists('/etc/pki/tls/openssl.cnf'):
return '/etc/pki/tls/openssl.cnf'
elif exists('/etc/ssl/openssl.cnf'):
return '/etc/ssl/openssl.cnf'
else:
raise Exception("Cannot find openssl config file")
def get_ip_addresses():
lines = subprocess.check_output('ip addr'.split(' '))
if not isinstance(lines, str):
lines = lines.decode('utf8')
for line in lines.split('\n'):
if line.startswith(' inet6 '):
line = line.replace(' inet6 ', '').split('/')[0]
if line == '::1':
continue
elif line.startswith(' inet '):
line = line.replace(' inet ', '').split('/')[0]
if line == '127.0.0.1':
continue
if line.startswith('169.254.'):
continue
else:
continue
yield line
def check_apache_config(path):
keypath = None
certpath = None
with open(path, 'r') as openf:
webconf = openf.read()
for line in webconf.split('\n'):
line = line.strip()
line = line.split('#')[0]
if line.startswith('SSLCertificateFile'):
_, certpath = line.split(None, 1)
if line.startswith('SSLCertificateKeyFile'):
_, keypath = line.split(None, 1)
return keypath, certpath
def get_certificate_paths():
keypath = None
certpath = None
if os.path.exists('/etc/httpd/conf.d/ssl.conf'): # redhat way
keypath, certpath = check_apache_config('/etc/httpd/conf.d/ssl.conf')
if not keypath and os.path.exists('/etc/apache2'): # suse way
for currpath, _, files in os.walk('/etc/apache2'):
for fname in files:
if fname.endswith('.template'):
continue
kploc = check_apache_config(os.path.join(currpath,
fname))
if keypath and kploc[0]:
return None, None # Ambiguous...
if kploc[0]:
keypath, certpath = kploc
return keypath, certpath
def create_certificate(keyout=None, certout=None):
if not keyout:
keyout, certout = get_certificate_paths()
if not keyout:
raise Exception('Unable to locate TLS certificate path automatically')
shortname = socket.gethostname().split('.')[0]
longname = socket.getfqdn()
subprocess.check_call(
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
keyout])
san = ['IP:{0}'.format(x) for x in get_ip_addresses()]
# It is incorrect to put IP addresses as DNS type. However
# there exists non-compliant clients that fail with them as IP
san.extend(['DNS:{0}'.format(x) for x in get_ip_addresses()])
san.append('DNS:{0}'.format(shortname))
san.append('DNS:{0}'.format(longname))
san = ','.join(san)
sslcfg = get_openssl_conf_location()
tmpconfig = tempfile.mktemp()
shutil.copy2(sslcfg, tmpconfig)
try:
with open(tmpconfig, 'a') as cfgfile:
cfgfile.write('\n[SAN]i\nbasicConstraints = CA:true\nsubjectAltName={0}'.format(san))
subprocess.check_call([
'openssl', 'req', '-new', '-x509', '-key', keyout, '-days',
'7300', '-out', certout, '-subj', '/CN={0}'.format(longname),
'-extensions', 'SAN', '-config', tmpconfig
])
finally:
os.remove(tmpconfig)
# Could restart the webserver now?
fname = '/var/lib/confluent/public/site/tls/{0}.pem'.format(
collective.get_myname())
try:
os.makedirs(os.path.dirname(fname))
except OSError as e:
if e.errno != 17:
raise
shutil.copy2(certout, fname)
hv = subprocess.check_output(
['openssl', 'x509', '-in', certout, '-hash', '-noout'])
if not isinstance(hv, str):
hv = hv.decode('utf8')
hv = hv.strip()
hashname = '/var/lib/confluent/public/site/tls/{0}.0'.format(hv)
certname = '{0}.pem'.format(collective.get_myname())
for currname in os.listdir('/var/lib/confluent/public/site/tls/'):
currname = os.path.join('/var/lib/confluent/public/site/tls/', currname)
if currname.endswith('.0'):
try:
realname = os.readlink(currname)
if realname == certname:
os.unlink(currname)
except OSError:
pass
os.symlink(certname, hashname)
if __name__ == '__main__':
outdir = os.getcwd()
keyout = os.path.join(outdir, 'key.pem')
certout = os.path.join(outdir, 'cert.pem')
create_certificate(keyout, certout)
@@ -97,6 +97,19 @@ node = {
'description': ('Classification of node as server or switch'),
'validvalues': ('switch', 'server'),
},
'crypted.rootpassword': {
'description': 'The password of the local root password. '
'This is stored as a non-recoverable hash.',
},
'crypted.selfapikey': {
'description': ('Crypt of api key for self api requests by node'),
},
'deployment.apiarmed': {
'description': ('Indicates whether the node authentication token interface '
'is armed. If set to once, it will grant only the next '
'request. If set to continuous, will allow many requests.'
'Should not be set unless an OS deployment is pending.'),
},
#'id': {
# 'description': ('Numeric identifier for node')
#},
@@ -157,6 +170,30 @@ node = {
'indicates candidate managers, either for '
'high availability or load balancing purposes.')
},
'deployment.pendingprofile': {
'description': ('An OS profile that is pending deployment. This indicates to '
'the network boot subsystem what should be offered when a potential '
'network boot request comes in')
},
'deployment.profile': {
'description': ('The profile that has most recently reported '
'completion of deployment. Note that an image may opt '
'to leave itself both current and pending, for example '
'a stateless profile would be both after first boot.')
},
'deployment.useinsecureprotocols': {
'description': ('What phase(s) of boot are permitted to use insecure protocols '
'(TFTP and HTTP without TLS. By default, HTTPS is allowed. However '
'this is not compatible with most firmware in most scenarios. Using '
'"firmware" as the setting will still use HTTPS after the initial download, '
'though be aware that a successful compromise during the firmware phase '
'will negate future TLS protections. The value "always" will result in '
'tftp/http being used for entire deployment. Note that ONIE does not '
'support secure protocols, and in that case this setting must be "always" '
'or "firmware"'),
'validlist': ('always', 'firmware', 'never'),
},
'discovery.passwordrules': {
'description': 'Any specified rules shall be configured on the BMC '
'upon discovery. "expiration=no,loginfailures=no,complexity=no,reuse=no" '
@@ -347,6 +384,19 @@ node = {
'description': 'Whether or not the indicated network interface is to be used for booting. This is used by '
'the discovery process to decide where to place the mac address of a detected PXE nic.',
},
'net.ipv4_address': {
'description': 'When configuring static, use this address. If '
'unspecified, it will check if the node name resolves '
'to an IP address. Additionally, the subnet prefix '
'may be specified with a suffix, e.g. "/16". If not '
'specified, it will attempt to autodetect based on '
'current network configuration.'
},
'net.ipv4_method': {
'description': 'Whether to use static or dhcp when configuring this '
'interface for IPv4.',
'validvalues': ('dhcp', 'static', 'none')
},
'net.ipv4_gateway': {
'description': 'The IPv4 gateway to use if applicable. As is the '
'case for other net attributes, net.eth0.ipv4_gateway '
@@ -441,4 +491,10 @@ node = {
'description': ('Fingerprint of the SSH key of the OS running on the '
'system.'),
},
'dns.domain': {
'description': 'DNS Domain searched by default by the system'
},
'dns.servers': {
'description': 'DNS Server or servers to provide to node',
},
}
@@ -1,4 +1,4 @@
7# vim: tabstop=4 shiftwidth=4 softtabstop=4
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2019 Lenovo
@@ -60,6 +60,7 @@ import confluent.util
import confluent.netutil as netutil
import confluent.exceptions as exc
import copy
import crypt
try:
import cPickle
except ModuleNotFoundError:
@@ -117,6 +118,7 @@ def attrib_supports_expression(attrib):
attrib = _attraliases.get(attrib, attrib)
if attrib.startswith('secret.') or attrib.startswith('crypted.'):
return False
return True
def _mkpath(pathname):
@@ -482,6 +484,13 @@ def _get_valid_attrname(attrname):
return attrname
def hashcrypt_value(value):
salt = confluent.util.stringify(base64.b64encode(os.urandom(12),
altchars=b'./'))
salt = '$6${0}'.format(salt)
return crypt.crypt(value, salt)
def crypt_value(value,
key=None,
integritykey=None):
@@ -495,7 +504,8 @@ def crypt_value(value,
key = _masterkey
iv = os.urandom(12)
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
value = confluent.util.stringify(value).encode('utf-8')
if not isinstance(value, bytes):
value = value.encode('utf-8')
cryptval, hmac = crypter.encrypt_and_digest(value)
return iv, cryptval, hmac, b'\x02'
@@ -1674,6 +1684,17 @@ class ConfigManager(object):
self.set_group_attributes(attribmap, autocreate=True)
def set_group_attributes(self, attribmap, autocreate=False):
for group in attribmap:
curr = attribmap[group]
for attrib in curr:
if attrib.startswith('crypted.'):
if not isinstance(curr[attrib], dict):
curr[attrib] = {'value': curr[attrib]}
if 'hashvalue' not in curr[attrib]:
curr[attrib]['hashvalue'] = hashcrypt_value(
curr[attrib]['value'])
if 'value' in curr[attrib]:
del curr[attrib]['value']
if cfgleader: # currently config slave to another
return exec_on_leader('_rpc_master_set_group_attributes',
self.tenant, attribmap, autocreate)
@@ -1767,6 +1788,9 @@ class ConfigManager(object):
if 'value' in newdict and attr.startswith("secret."):
newdict['cryptvalue'] = crypt_value(newdict['value'])
del newdict['value']
if 'value' in newdict and attr.startswith("crypted."):
newdict['hashvalue'] = hashcrypt_value(newdict['value'])
del newdict['value']
cfgobj[attr] = newdict
if attr == 'nodes':
self._sync_nodes_to_group(group=group,
@@ -2079,6 +2103,17 @@ class ConfigManager(object):
def set_node_attributes(self, attribmap, autocreate=False):
for node in attribmap:
curr = attribmap[node]
for attrib in curr:
if attrib.startswith('crypted.'):
if not isinstance(curr[attrib], dict):
curr[attrib] = {'value': curr[attrib]}
if 'hashvalue' not in curr[attrib]:
curr[attrib]['hashvalue'] = hashcrypt_value(
curr[attrib]['value'])
if 'value' in curr[attrib]:
del curr[attrib]['value']
if cfgleader: # currently config slave to another
return exec_on_leader('_rpc_master_set_node_attributes',
self.tenant, attribmap, autocreate)
@@ -2169,6 +2204,9 @@ class ConfigManager(object):
if 'value' in newdict and attrname.startswith("secret."):
newdict['cryptvalue'] = crypt_value(newdict['value'])
del newdict['value']
if 'value' in newdict and attrname.startswith("crypted."):
newdict['hashvalue'] = hashcrypt_value(newdict['value'])
del newdict['value']
cfgobj[attrname] = newdict
if attrname == 'groups':
self._sync_groups_to_node(node=node,
+3 -3
View File
@@ -108,10 +108,10 @@ def pytechars2line(chars, maxlen=None):
char = chars[charidx]
csi = bytearray([])
if char.fg != lfg:
csi.append(30 + pytecolors2ansi[char.fg])
csi.append(30 + pytecolors2ansi.get(char.fg, 9))
lfg = char.fg
if char.bg != lbg:
csi.append(40 + pytecolors2ansi[char.bg])
csi.append(40 + pytecolors2ansi.get(char.bg, 9))
lbg = char.bg
if char.bold != lb:
lb = char.bold
@@ -243,7 +243,7 @@ class ConsoleHandler(object):
def check_collective(self, attrvalue):
myc = attrvalue.get(self.node, {}).get('collective.manager', {}).get(
'value', None)
if configmodule.list_collective() and not myc:
if list(configmodule.list_collective()) and not myc:
self._is_local = False
self._detach()
self._disconnect()
+52 -6
View File
@@ -46,6 +46,7 @@ import confluent.exceptions as exc
import confluent.messages as msg
import confluent.networking.macmap as macmap
import confluent.noderange as noderange
import confluent.osimage as osimage
try:
import confluent.shellmodule as shellmodule
except ImportError:
@@ -69,6 +70,10 @@ import sys
pluginmap = {}
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol')
try:
unicode
except NameError:
unicode = str
def seek_element(currplace, currkey):
try:
@@ -125,8 +130,9 @@ def load_plugins():
sys.path.pop(1)
rootcollections = ['discovery/', 'events/', 'networking/',
'noderange/', 'nodes/', 'nodegroups/', 'usergroups/' , 'users/', 'version']
rootcollections = ['deployment/', 'discovery/', 'events/', 'networking/',
'noderange/', 'nodes/', 'nodegroups/', 'usergroups/' ,
'users/', 'version']
class PluginRoute(object):
@@ -138,6 +144,47 @@ class PluginCollection(object):
def __init__(self, routedict):
self.routeinfo = routedict
def handle_deployment(configmanager, inputdata, pathcomponents,
operation):
if len(pathcomponents) == 1:
yield msg.ChildCollection('distributions/')
yield msg.ChildCollection('profiles/')
yield msg.ChildCollection('importing/')
return
if pathcomponents[1] == 'distributions':
if len(pathcomponents) == 2:
for dist in osimage.list_distros():
yield msg.ChildCollection(dist + '/')
return
if pathcomponents[1] == 'profiles':
if len(pathcomponents) == 2:
for prof in osimage.list_profiles():
yield msg.ChildCollection(prof + '/')
return
if pathcomponents[1] == 'importing':
if len(pathcomponents) == 2 or not pathcomponents[-1]:
if operation == 'retrieve':
for imp in osimage.list_importing():
yield imp
return
elif operation == 'create':
importer = osimage.MediaImporter(inputdata['filename'])
yield msg.KeyValueData({'target': importer.targpath,
'name': importer.importkey})
return
elif len(pathcomponents) == 3:
if operation == 'retrieve':
for res in osimage.get_importing_status(pathcomponents[-1]):
yield res
return
elif operation == 'delete':
for res in osimage.remove_importing(pathcomponents[-1]):
yield res
return
raise exc.NotFoundException('Unrecognized request')
def _init_core():
global noderesources
global nodegroupresources
@@ -1056,10 +1103,6 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
yield rsp
def handle_discovery(pathcomponents, operation, configmanager, inputdata):
if pathcomponents[0] == 'detected':
pass
def handle_discovery(pathcomponents, operation, configmanager, inputdata):
if pathcomponents[0] == 'detected':
pass
@@ -1080,6 +1123,9 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
elif pathcomponents[0] == 'noderange':
return handle_node_request(configmanager, inputdata, operation,
pathcomponents, autostrip)
elif pathcomponents[0] == 'deployment':
return handle_deployment(configmanager, inputdata, pathcomponents,
operation)
elif pathcomponents[0] == 'nodegroups':
return handle_nodegroup_request(configmanager, inputdata,
pathcomponents,
+84
View File
@@ -0,0 +1,84 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.config.configmanager as cfm
import confluent.netutil as netutil
import confluent.util as util
import datetime
import eventlet
import eventlet.green.socket as socket
import eventlet.greenpool
import os
class CredServer(object):
def __init__(self):
self.cfm = cfm.ConfigManager(None)
def handle_client(self, client, peer):
try:
if not netutil.address_is_local(peer[0]):
client.close()
return
client.send(b'\xc2\xd1-\xa8\x80\xd8j\xba')
tlv = bytearray(client.recv(2))
if tlv[0] != 1:
client.close()
return
nodename = util.stringify(client.recv(tlv[1]))
tlv = bytearray(client.recv(2))
apiarmed = self.cfm.get_node_attributes(nodename, 'deployment.apiarmed')
apiarmed = apiarmed.get(nodename, {}).get('deployment.apiarmed', {}).get(
'value', None)
if not apiarmed:
client.close()
return
if apiarmed not in ('once', 'continuous'):
now = datetime.datetime.utcnow()
expiry = datetime.datetime.strptime(apiarmed, "%Y-%m-%dT%H:%M:%SZ")
if now > expiry:
self.cfm.set_node_attributes({nodename: {'deployment.apiarmed': ''}})
client.close()
return
client.send(b'\x02\x20')
rttoken = os.urandom(32)
client.send(rttoken)
client.send(b'\x00\x00')
tlv = bytearray(client.recv(2))
if tlv[0] != 3:
client.close()
return
echotoken = client.recv(tlv[1])
if echotoken != rttoken:
client.close()
return
tlv = bytearray(client.recv(2))
if tlv[0] != 4:
client.close()
return
echotoken = util.stringify(client.recv(tlv[1]))
cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.apiarmed': ''}}
if apiarmed == 'continuous':
del cfgupdate[nodename]['deployment.apiarmed']
self.cfm.set_node_attributes(cfgupdate)
client.recv(2) # drain end of message
client.send(b'\x05\x00') # report success
finally:
client.close()
if __name__ == '__main__':
a = CredServer()
while True:
eventlet.sleep(86400)
+11 -10
View File
@@ -704,11 +704,11 @@ def detected(info):
if nodename and handler:
eval_node(cfg, handler, info, nodename)
elif handler:
log.log(
{'info': 'Detected unknown {0} with hwaddr {1} at '
'address {2}'.format(
handler.devname, info['hwaddr'], handler.ipaddr
)})
#log.log(
# {'info': 'Detected unknown {0} with hwaddr {1} at '
# 'address {2}'.format(
# handler.devname, info['hwaddr'], handler.ipaddr
# )})
info['discostatus'] = 'unidentified'
unknown_info[info['hwaddr']] = info
@@ -877,6 +877,8 @@ def get_nodename_from_chained_smms(cfg, handler, info):
nodename = newnodename
return nodename
def get_node_by_uuid(uuid):
return nodes_by_uuid.get(uuid, None)
def get_nodename_from_enclosures(cfg, info):
nodename = None
@@ -1076,7 +1078,7 @@ def discover_node(cfg, handler, info, nodename, manual):
traceback.print_exc()
return False
newnodeattribs = {}
if cfm.list_collective():
if list(cfm.list_collective()):
# We are in a collective, check collective.manager
cmc = cfg.get_node_attributes(nodename, 'collective.manager')
cm = cmc.get(nodename, {}).get('collective.manager', {}).get('value', None)
@@ -1140,8 +1142,8 @@ def do_pxe_discovery(cfg, handler, info, manual, nodename, policies):
return True
if uuid_is_valid(info['uuid']):
known_pxe_uuids[info['uuid']] = nodename
log.log({'info': 'Detected {0} ({1} with mac {2})'.format(
nodename, handler.devname, info['hwaddr'])})
#log.log({'info': 'Detected {0} ({1} with mac {2})'.format(
# nodename, handler.devname, info['hwaddr'])})
return True
@@ -1246,8 +1248,7 @@ def start_detection():
if rechecker is None:
rechecktime = util.monotonic_time() + 900
rechecker = eventlet.spawn_after(900, _periodic_recheck, cfg)
# eventlet.spawn_n(ssdp.snoop, safe_detected)
eventlet.spawn_n(ssdp.snoop, None, None, ssdp, get_node_by_uuid)
def stop_autosense():
for watcher in list(autosensors):
@@ -22,18 +22,141 @@
# option 97 = UUID (wireformat)
import confluent.config.configmanager as cfm
import confluent.log as log
import confluent.netutil as netutil
import ctypes
import ctypes.util
import eventlet
import eventlet.green.socket as socket
import eventlet.green.select as select
import netifaces
import struct
libc = ctypes.CDLL(ctypes.util.find_library('c'))
iphdr = b'\x45\x00\x00\x00\x00\x00\x00\x00\x40\x11\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff'
constiphdrsum = b'\x85\x11'
udphdr = b'\x00\x43\x00\x44\x00\x00\x00\x00'
def _ipsum(data):
currsum = 0
if len(data) % 2:
currsum = struct.unpack('!B', data[-1:])[0] << 8
data = memoryview(data)
data = data[:-1]
for datum in struct.unpack('!' + 'H' * (len(data) // 2), data):
currsum += datum
if currsum >> 16:
currsum &= 0xffff
currsum += 1
if currsum == 0:
currsum = 0xffff
return currsum
class sockaddr_ll(ctypes.Structure):
_fields_ = [('sll_family', ctypes.c_ushort),
('sll_protocol', ctypes.c_ushort),
('sll_ifindex', ctypes.c_int),
('sll_hatype', ctypes.c_ushort),
('sll_pkttype', ctypes.c_ubyte),
('sll_halen', ctypes.c_ubyte),
('sll_addr', ctypes.c_ubyte * 20)]
class iovec(ctypes.Structure): # from uio.h
_fields_ = [('iov_base', ctypes.c_void_p),
('iov_len', ctypes.c_size_t)]
class msghdr(ctypes.Structure): # from bits/socket.h
_fields_ = [('msg_name', ctypes.c_void_p),
('msg_namelen', ctypes.c_uint),
('msg_iov', ctypes.POINTER(iovec)),
('msg_iovlen', ctypes.c_size_t),
('msg_control', ctypes.c_void_p),
('msg_controllen', ctypes.c_size_t),
('msg_flags', ctypes.c_int)]
class cmsghdr(ctypes.Structure): # also from bits/socket.h
_fields_ = [('cmsg_len', ctypes.c_size_t),
('cmsg_level', ctypes.c_int),
('cmsg_type', ctypes.c_int)]
# ignore the __extension__
class in_addr(ctypes.Structure):
_fields_ = [('s_addr', ctypes.c_uint32)]
class in_pktinfo(ctypes.Structure): # from bits/in.h
_fields_ = [('ipi_ifindex', ctypes.c_int),
('ipi_spec_dst', in_addr),
('ipi_addr', in_addr)]
class sockaddr_in(ctypes.Structure):
_fields_ = [('sin_family', ctypes.c_ushort), # per bits/sockaddr.h
('sin_port', ctypes.c_uint16), # per netinet/in.h
('sin_addr', in_addr)]
sendto = libc.sendto
sendto.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t,
ctypes.c_int, ctypes.POINTER(sockaddr_ll),
ctypes.c_size_t]
sendto.restype = ctypes.c_size_t
recvmsg = libc.recvmsg
recvmsg.argtypes = [ctypes.c_int, ctypes.POINTER(msghdr), ctypes.c_int]
recvmsg.restype = ctypes.c_size_t
pkttype = ctypes.c_char * 2048
_idxtoname = libc.if_indextoname
_idxtoname.argtypes = [ctypes.c_uint, ctypes.c_char_p]
def idxtoname(idx):
name = (ctypes.c_char * 16)()
_idxtoname(idx, name)
ret = name.value.strip()
if not isinstance(ret, str):
ret = ret.decode('utf8')
return ret
_idxtobcast = {}
def get_bcastaddr(idx):
if idx not in _idxtobcast:
bc = netifaces.ifaddresses(idxtoname(idx))[17][0]['broadcast']
bc = bytearray([int(x, 16) for x in bc.split(':')])
_idxtobcast[idx] = bc
return _idxtobcast[idx]
IP_PKTINFO = 8
def CMSG_ALIGN(length): # bits/socket.h
ret = (length + ctypes.sizeof(ctypes.c_size_t) - 1
& ~(ctypes.sizeof(ctypes.c_size_t) - 1))
return ctypes.c_size_t(ret)
def CMSG_SPACE(length): # bits/socket.h
ret = CMSG_ALIGN(length).value + CMSG_ALIGN(ctypes.sizeof(cmsghdr)).value
return ctypes.c_size_t(ret)
cmsgtype = ctypes.c_char * CMSG_SPACE(ctypes.sizeof(in_pktinfo)).value
cmsgsize = CMSG_SPACE(ctypes.sizeof(in_pktinfo)).value
pxearchs = {
'\x00\x00': 'bios-x86',
'\x00\x07': 'uefi-x64',
'\x00\x09': 'uefi-x64',
'\x00\x0b': 'uefi-aarch64',
'\x00\x10': 'uefi-httpboot',
b'\x00\x00': 'bios-x86',
b'\x00\x07': 'uefi-x64',
b'\x00\x09': 'uefi-x64',
b'\x00\x0b': 'uefi-aarch64',
b'\x00\x10': 'uefi-httpboot',
}
uuidmap = {}
macmap = {}
attribwatcher = None
def stringify(value):
string = bytes(value)
if not isinstance(string, str):
@@ -60,109 +183,415 @@ def _decode_ocp_vivso(rq, idx, size):
idx += rq[idx + 1] + 2
return '', None, vivso
def find_info_in_options(rq, optidx):
uuid = None
arch = None
vivso = None
ztpurlrequested = False
iscumulus = False
def opts_to_dict(rq, optidx, expectype=1):
reqdict = {}
disco = {'uuid':None, 'arch': None, 'vivso': None}
try:
while uuid is None or arch is None:
if rq[optidx] == 53: # DHCP message type
# we want only length 1 and only discover (type 1)
if rq[optidx + 1] != 1 or rq[optidx + 2] != 1:
return uuid, arch, vivso
optidx += 3
elif rq[optidx] == 55:
if 239 in rq[optidx + 2:optidx + 2 + rq[optidx + 1]]:
ztpurlrequested = True
optidx += rq[optidx + 1] + 2
elif rq[optidx] == 60:
vci = stringify(rq[optidx + 2:optidx + 2 + rq[optidx + 1]])
if vci.startswith('cumulus-linux'):
iscumulus = True
arch = vci.replace('cumulus-linux', '').strip()
optidx += rq[optidx + 1] + 2
elif rq[optidx] == 97:
if rq[optidx + 1] != 17:
# 16 bytes of uuid and one reserved byte
return uuid, arch, vivso
if rq[optidx + 2] != 0: # the reserved byte should be zero,
# anything else would be a new spec that we don't know yet
return uuid, arch, vivso
uuid = decode_uuid(rq[optidx + 3:optidx + 19])
optidx += 19
elif rq[optidx] == 93:
if rq[optidx + 1] != 2:
return uuid, arch
archraw = bytes(rq[optidx + 2:optidx + 4])
if archraw in pxearchs:
arch = pxearchs[archraw]
optidx += 4
elif rq[optidx] == 125:
#vivso = rq[optidx + 2:optidx + 2 + rq[optidx + 1]]
if rq[optidx + 2:optidx + 6] == b'\x00\x00\xa6\x7f': # OCP
return _decode_ocp_vivso(rq, optidx + 7, rq[optidx + 6])
optidx += rq[optidx + 1] + 2
else:
optidx += rq[optidx + 1] + 2
while optidx < len(rq):
optnum = rq[optidx]
optlen = rq[optidx + 1]
reqdict[optnum] = rq[optidx + 2:optidx + 2 + optlen]
optidx += optlen + 2
except IndexError:
pass
if not vivso and iscumulus and ztpurlrequested:
if not uuid:
uuid = ''
vivso = {'service-type': 'cumulus-switch', 'arch': arch}
return uuid, arch, vivso
if reqdict.get(53, [0])[0] != expectype:
return reqdict, disco
# It is a discover packet..
iscumulus = False
maybeztp = False
if 239 in reqdict.get(55, []):
maybeztp = True
vci = stringify(reqdict.get(60, b''))
if vci.startswith('cumulus-linux'):
disco['arch'] = vci.replace('cumulus-linux', '').strip()
iscumulus = True
if reqdict.get(93, None):
disco['arch'] = pxearchs.get(bytes(reqdict[93]), None)
if reqdict.get(97, None):
uuidcandidate = reqdict[97]
if uuidcandidate[0] != 0:
return reqdict, disco
disco['uuid'] = decode_uuid(uuidcandidate[1:])
if reqdict.get(125, None):
if reqdict[125][:4] == b'\x00\x00\xa6\x7f': # OCP
disco['vivso'] = _decode_ocp_vivso(
reqdict[125], 5, reqdict[125][4])[-1]
return reqdict, disco
if not disco['vivso'] and iscumulus and maybeztp:
if not disco['uuid']:
disco['uuid'] = ''
disco['vivso'] = {'service-type': 'cumulus-switch',
'arch': disco['arch']}
return reqdict, disco
def ipfromint(numb):
return socket.inet_ntoa(struct.pack('I', numb))
def proxydhcp():
net4011 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
net4011.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
net4011.setsockopt(socket.IPPROTO_IP, IP_PKTINFO, 1)
net4011.bind(('', 4011))
cfg = cfm.ConfigManager(None)
while True:
ready = select.select([net4011], [], [], None)
if not ready or not ready[0]:
continue
rq = bytearray(1024)
rqv = memoryview(rq)
nb, client = net4011.recvfrom_into(rq)
if nb < 240:
continue
rp = bytearray(1024)
rpv = memoryview(rp)
try:
optidx = rq.index(b'\x63\x82\x53\x63') + 4
except ValueError:
continue
opts, disco = opts_to_dict(rq, optidx, 3)
disco['uuid']
node = None
if disco.get('hwaddr', None) in macmap:
node = macmap[disco['hwaddr']]
elif disco.get('uuid', None) in uuidmap:
node = uuidmap[disco['uuid']]
if not node:
continue
hwlen = rq[2]
myipn = myipbypeer.get(rqv[28:28+hwlen].tobytes(), None)
if not myipn:
continue
if opts.get(77, None) == b'iPXE':
cfd = cfg.get_node_attributes(node, ('deployment.*'))
profile = cfd.get(node, {}).get(
'deployment.pendingprofile', {}).get('value', None)
if not profile:
continue
myip = socket.inet_ntoa(myipn)
bootfile = 'http://{0}/confluent-public/os/{1}/boot/boot.ipxe'.format(myip, profile).encode('utf8')
elif disco['arch'] == 'uefi-x64':
bootfile = b'confluent/x86_64/ipxe.efi'
elif disco['arch'] == 'bios-x86':
bootfile = b'confluent/x86_64/ipxe.kkpxe'
rpv[:240] = rqv[:240].tobytes()
rpv[0:1] = b'\x02'
rpv[108:108 + len(bootfile)] = bootfile
rpv[240:243] = b'\x35\x01\x05'
rpv[243:249] = b'\x36\x04' + myipn
rpv[20:24] = myipn
rpv[249:268] = b'\x61\x11' + opts[97]
rpv[268:280] = b'\x3c\x09PXEClient\xff'
net4011.sendto(rpv[:281], client)
def start_proxydhcp():
eventlet.spawn_n(proxydhcp)
def snoop(handler, protocol=None):
#TODO(jjohnson2): ipv6 socket and multicast for DHCPv6, should that be
#prominent
#TODO(jjohnson2): IP_PKTINFO, recvmsg to get the destination ip, per
#proxydhcp.c from xCAT
#TODO(jjohnson2): enable unicast replies. This would suggest either
# injection into the neigh table before OFFER or using SOCK_RAW.
global attribwatcher
cfg = cfm.ConfigManager(None)
remap_nodes(cfg.list_nodes(), cfg)
attribwatcher = cfg.watch_attributes(cfg.list_nodes(), ('id.uuid', 'net.*hwaddr'), remap_nodes)
cfg.watch_nodecollection(new_nodes)
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
net4.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
net4.setsockopt(socket.IPPROTO_IP, IP_PKTINFO, 1)
net4.bind(('', 67))
while True:
# Just need some delay, picked a prime number so that overlap with other
# timers might be reduced, though it really is probably nothing
(rq, peer) = net4.recvfrom(9000)
ready = select.select([net4], [], [], None)
if not ready or not ready[0]:
continue
clientaddr = sockaddr_in()
rawbuffer = bytearray(2048)
data = pkttype.from_buffer(rawbuffer)
msg = msghdr()
cmsgarr = bytearray(cmsgsize)
cmsg = cmsgtype.from_buffer(cmsgarr)
iov = iovec()
iov.iov_base = ctypes.addressof(data)
iov.iov_len = 2048
msg.msg_iov = ctypes.pointer(iov)
msg.msg_iovlen = 1
msg.msg_control = ctypes.addressof(cmsg)
msg.msg_controllen = ctypes.sizeof(cmsg)
msg.msg_name = ctypes.addressof(clientaddr)
msg.msg_namelen = ctypes.sizeof(clientaddr)
# We'll leave name and namelen blank for now
i = recvmsg(net4.fileno(), ctypes.pointer(msg), 0)
# if we have a small packet, just skip, it can't possible hold enough
# data and avoids some downstream IndexErrors that would be messy
# with try/except
if len(rq) < 64:
if i < 64:
continue
rq = bytearray(rq)
#peer = ipfromint(clientaddr.sin_addr.s_addr)
# We don't need peer yet, generally it's 0.0.0.0
_, level, typ = struct.unpack('QII', cmsgarr[:16])
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
idx, recv, targ = struct.unpack('III', cmsgarr[16:28])
recv = ipfromint(recv)
targ = ipfromint(targ)
# peer is the source ip (in dhcpdiscover, 0.0.0.0)
# recv is the 'ip' that recevied the packet, regardless of target
# targ is the ip in the destination ip of the header.
# idx is the ip link number of the receiving nic
# For example, a DHCPDISCOVER will probably have:
# peer of 0.0.0.0
# targ of 255.255.255.255
# recv of <actual ip address that could reply>
# idx correlated to the nic
rqv = memoryview(rawbuffer)
rq = bytearray(rqv[:i])
if rq[0] == 1: # Boot request
addrlen = rq[2]
if addrlen > 16 or addrlen == 0:
continue
netaddr = rq[28:28+addrlen]
netaddr = ':'.join(['{0:02x}'.format(x) for x in netaddr])
rawnetaddr = rq[28:28+addrlen]
netaddr = ':'.join(['{0:02x}'.format(x) for x in rawnetaddr])
optidx = 0
try:
optidx = rq.index(b'\x63\x82\x53\x63') + 4
except ValueError:
continue
uuid, arch, vivso = find_info_in_options(rq, optidx)
txid = rq[4:8] # struct.unpack('!I', rq[4:8])[0]
rqinfo, disco = opts_to_dict(rq, optidx)
vivso = disco.get('vivso', None)
if vivso:
# info['modelnumber'] = info['attributes']['enclosure-machinetype-model'][0]
handler({'hwaddr': netaddr, 'uuid': uuid,
'architecture': vivso.get('arch', ''),
'services': (vivso['service-type'],),
'attributes': {'enclosure-machinetype-model': [vivso.get('machine', '')]}})
continue
if uuid is None:
info = {'hwaddr': netaddr, 'uuid': disco['uuid'],
'architecture': vivso.get('arch', ''),
'services': (vivso['service-type'],),
'netinfo': {'ifidx': idx, 'recvip': recv, 'txid': txid},
'attributes': {'enclosure-machinetype-model': [vivso.get('machine', '')]}}
handler(info)
#consider_discover(info, rqinfo, net4, cfg, rqv)
continue
# We will fill out service to have something to byte into,
# but the nature of the beast is that we do not have peers,
# so that will not be present for a pxe snoop
handler({'hwaddr': netaddr, 'uuid': uuid, 'architecture': arch,
'services': ('pxe-client',)})
info = {'hwaddr': netaddr, 'uuid': disco['uuid'],
'architecture': disco['arch'],
'netinfo': {'ifidx': idx, 'recvip': recv, 'txid': txid},
'services': ('pxe-client',)}
if disco['uuid']: #TODO(jjohnson2): need to explictly check for
# discover, so that the parser can go ahead and
# parse the options including uuid to enable
# ACK
handler(info)
consider_discover(info, rqinfo, net4, cfg, rqv)
def clear_nodes(nodes):
for nodename in nodes:
for ent in list(macmap):
if macmap[ent] == nodename:
del macmap[ent]
for ent in list(uuidmap):
if uuidmap[ent] == nodename:
del uuidmap[ent]
def new_nodes(added, deleting, renamed, configmanager):
global attribwatcher
configmanager.remove_watcher(attribwatcher)
alldeleting = set(deleting) | set(renamed)
clear_nodes(alldeleting)
attribwatcher = configmanager.watch_attributes(configmanager.list_nodes(),
('id.uuid', 'net.*hwaddr'), remap_nodes)
def remap_nodes(nodeattribs, configmanager):
global macmap
global uuidmap
updates = configmanager.get_node_attributes(nodeattribs, ('id.uuid', 'net.*hwaddr'))
clear_nodes(nodeattribs)
for node in updates:
for attrib in updates[node]:
if attrib == 'id.uuid':
uuidmap[updates[node][attrib]['value']] = node
elif 'hwaddr' in attrib:
macmap[updates[node][attrib]['value']] = node
staticassigns = {}
myipbypeer = {}
def check_reply(node, info, packet, sock, cfg, reqview):
httpboot = info['architecture'] == 'uefi-httpboot'
replen = 275 # default is going to be 286
cfd = cfg.get_node_attributes(node, ('deployment.*'))
profile = cfd.get(node, {}).get('deployment.pendingprofile', {}).get('value', None)
myipn = info['netinfo']['recvip']
myipn = socket.inet_aton(myipn)
if not profile:
return
rqtype = packet[53][0]
insecuremode = cfd.get(node, {}).get('deployment.useinsecureprotocols',
'never')
if not insecuremode:
insecuremode = 'never'
if insecuremode == 'never' and not httpboot:
if rqtype == 1 and info['architecture']:
log.log(
{'info': 'Boot attempt by {0} detected in insecure mode, but '
'insecure mode is disabled. Set the attribute '
'`deployment.useinsecureprotocols` to `firmware` or '
'`always` to enable support, or use UEFI HTTP boot '
'with HTTPS.'.format(node)})
return
reply = bytearray(512)
repview = memoryview(reply)
repview[:20] = iphdr
repview[12:16] = myipn
repview[20:28] = udphdr
repview = repview[28:]
repview[0:1] = b'\x02'
repview[1:10] = reqview[1:10] # duplicate txid, hwlen, and others
repview[10:11] = b'\x80' # always set broadcast
repview[28:44] = reqview[28:44] # copy chaddr field
if httpboot:
proto = 'https' if insecuremode == 'never' else 'http'
bootfile = '{0}://{1}/confluent-public/os/{2}/boot.img'.format(
proto, info['netinfo']['recvip'], profile
)
if not isinstance(bootfile, bytes):
bootfile = bootfile.encode('utf8')
repview[108:108 + len(bootfile)] = bootfile
repview[20:24] = myipn
gateway = None
netmask = None
niccfg = netutil.get_nic_config(cfg, node, ifidx=info['netinfo']['ifidx'])
if niccfg.get('ipv4_broken', False):
# Received a request over a nic with no ipv4 configured, ignore it
return
clipn = None
if niccfg['ipv4_address']:
clipn = socket.inet_aton(niccfg['ipv4_address'])
repview[16:20] = clipn
gateway = niccfg['ipv4_gateway']
if gateway:
gateway = socket.inet_aton(gateway)
netmask = niccfg['prefix']
netmask = (2**32 - 1) ^ (2**(32 - netmask) - 1)
netmask = struct.pack('!I', netmask)
repview[236:240] = b'\x63\x82\x53\x63'
repview[240:242] = b'\x35\x01'
if rqtype == 1: # if discover, then offer
repview[242:243] = b'\x02'
elif rqtype == 3: # if request, then ack
repview[242:243] = b'\x05'
repview[243:245] = b'\x36\x04' # DHCP server identifier
repview[245:249] = myipn
repview[249:255] = b'\x33\x04\x00\x00\x00\xf0' # fixed short lease time
repview[255:257] = b'\x61\x11'
repview[257:274] = packet[97]
# Note that sending PXEClient kicks off the proxyDHCP procedure, ignoring
# boot filename and such in the DHCP packet
# we will simply always do it to provide the boot payload in a consistent
# matter to both dhcp-elsewhere and fixed ip clients
if info['architecture'] == 'uefi-httpboot':
repview[replen - 1:replen + 11] = b'\x3c\x0aHTTPClient'
replen += 12
else:
repview[replen - 1:replen + 10] = b'\x3c\x09PXEClient'
replen += 11
hwlen = bytearray(reqview[2:3].tobytes())[0]
fulladdr = repview[28:28+hwlen].tobytes()
myipbypeer[fulladdr] = myipn
if hwlen == 8: # omnipath may present a mangled proxydhcp request later
shortaddr = bytearray(6)
shortaddr[0] = 2
shortaddr[1:] = fulladdr[3:]
myipbypeer[bytes(shortaddr)] = myipn
if netmask:
repview[replen - 1:replen + 1] = b'\x01\x04'
repview[replen + 1:replen + 5] = netmask
replen += 6
if gateway:
repview[replen - 1:replen + 1] = b'\x03\x04'
repview[replen + 1:replen + 5] = gateway
replen += 6
repview[replen - 1:replen] = b'\xff' # end of options, should always be last byte
repview = memoryview(reply)
pktlen = struct.pack('!H', replen + 28) # ip+udp = 28
repview[2:4] = pktlen
curripsum = ~(_ipsum(constiphdrsum + pktlen + myipn)) & 0xffff
repview[10:12] = struct.pack('!H', curripsum)
repview[24:26] = struct.pack('!H', replen + 8)
datasum = _ipsum(b'\x00\x11' + repview[24:26].tobytes() +
repview[12:replen + 28].tobytes())
datasum = ~datasum & 0xffff
repview[26:28] = struct.pack('!H', datasum)
if clipn:
staticassigns[fulladdr] = (clipn, repview[:replen + 28].tobytes())
elif fulladdr in staticassigns:
del staticassigns[fulladdr]
send_raw_packet(repview, replen + 28, reqview, info)
def send_raw_packet(repview, replen, reqview, info):
ifidx = info['netinfo']['ifidx']
tsock = socket.socket(socket.AF_PACKET, socket.SOCK_DGRAM,
socket.htons(0x800))
targ = sockaddr_ll()
bcastaddr = get_bcastaddr(ifidx)
hwlen = len(bcastaddr)
bcastaddr20 = bytearray(20)
bcastaddr20[:hwlen] = bcastaddr
targ.sll_addr = (ctypes.c_ubyte * 20).from_buffer(bcastaddr20)
targ.sll_family = socket.AF_PACKET
targ.sll_halen = hwlen
targ.sll_protocol = socket.htons(0x800)
targ.sll_ifindex = ifidx
try:
pkt = ctypes.byref((ctypes.c_char * (replen)).from_buffer(repview))
except TypeError:
# Python 2....
pkt = ctypes.byref((ctypes.c_char * (replen)).from_buffer_copy(
repview[:replen].tobytes()))
sendto(tsock.fileno(), pkt, replen, 0, ctypes.byref(targ),
ctypes.sizeof(targ))
def ack_request(pkt, rq, info):
hwlen = bytearray(rq[2:3].tobytes())[0]
hwaddr = rq[28:28+hwlen].tobytes()
myipn = myipbypeer.get(hwaddr, None)
if not myipn or pkt.get(54, None) != myipn:
return
assigninfo = staticassigns.get(hwaddr, None)
if assigninfo == None:
return
if pkt.get(50, None) != assigninfo[0]:
return
rply = assigninfo[1]
reply = bytearray(512)
repview = memoryview(reply)
repview[:len(rply)] = rply
repview[270:271] = b'\x05'
repview[26:28] = struct.pack('!H', 0) # TODO: use datasum, it was incorrect)
datasum = _ipsum(b'\x00\x11' + repview[24:26].tobytes() +
repview[12:len(rply)].tobytes())
datasum = ~datasum & 0xffff
repview[26:28] = struct.pack('!H', datasum)
send_raw_packet(repview, len(rply), rq, info)
def consider_discover(info, packet, sock, cfg, reqview):
if info.get('hwaddr', None) in macmap and info.get('uuid', None):
check_reply(macmap[info['hwaddr']], info, packet, sock, cfg, reqview)
elif info.get('uuid', None) in uuidmap:
check_reply(uuidmap[info['uuid']], info, packet, sock, cfg, reqview)
elif packet.get(53, None) == b'\x03':
ack_request(packet, reqview, info)
if __name__ == '__main__':
def testsnoop(info):
print(repr(info))
snoop(testsnoop)
snoop(testsnoop)
@@ -30,13 +30,16 @@
import confluent.neighutil as neighutil
import confluent.util as util
import confluent.log as log
import eventlet.green.select as select
import eventlet.green.socket as socket
import time
try:
from eventlet.green.urllib.request import urlopen
except (ImportError, AssertionError):
from eventlet.green.urllib2 import urlopen
import struct
import traceback
mcastv4addr = '239.255.255.250'
mcastv6addr = 'ff02::c'
@@ -69,7 +72,7 @@ def scan(services, target=None):
yield rply
def snoop(handler, byehandler=None):
def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
"""Watch for SSDP notify messages
The handler shall be called on any service coming online.
@@ -85,6 +88,7 @@ def snoop(handler, byehandler=None):
# Normally, I like using v6/v4 agnostic socket. However, since we are
# dabbling in multicast wizardry here, such sockets can cause big problems,
# so we will have two distinct sockets
tracelog = log.Logger('trace')
known_peers = set([])
net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
@@ -108,50 +112,82 @@ def snoop(handler, byehandler=None):
net6.bind(('', 1900))
peerbymacaddress = {}
while True:
newmacs = set([])
machandlers = {}
r, _, _ = select.select((net4, net6), (), (), 60)
neighutil.update_neigh()
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
rsp = rsp.split('\r\n')
method, _, _ = rsp[0].split(' ', 2)
if method == 'NOTIFY':
ip = peer[0].partition('%')[0]
if ip not in neighutil.neightable:
try:
newmacs = set([])
machandlers = {}
r, _, _ = select.select((net4, net6), (), (), 60)
neighutil.update_neigh()
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
if rsp[:4] == b'PING':
continue
if peer in known_peers:
continue
mac = neighutil.neightable[ip]
known_peers.add(peer)
newmacs.add(mac)
if mac in peerbymacaddress:
peerbymacaddress[mac]['addresses'].append(peer)
else:
peerbymacaddress[mac] = {
'hwaddr': mac,
'addresses': [peer],
}
peerdata = peerbymacaddress[mac]
rsp = rsp.split(b'\r\n')
method, _, _ = rsp[0].split(b' ', 2)
if method == b'NOTIFY':
ip = peer[0].partition('%')[0]
if ip not in neighutil.neightable:
continue
if peer in known_peers:
continue
mac = neighutil.neightable[ip]
known_peers.add(peer)
newmacs.add(mac)
if mac in peerbymacaddress:
peerbymacaddress[mac]['addresses'].append(peer)
else:
peerbymacaddress[mac] = {
'hwaddr': mac,
'addresses': [peer],
}
peerdata = peerbymacaddress[mac]
for headline in rsp[1:]:
if not headline:
continue
headline = util.stringify(headline)
header, _, value = headline.partition(':')
header = header.strip()
value = value.strip()
if header == 'NT':
peerdata['service'] = value
elif header == 'NTS':
if value == 'ssdp:byebye':
machandlers[mac] = byehandler
elif value == 'ssdp:alive':
machandlers[mac] = None # handler
elif method == b'M-SEARCH':
if not uuidlookup:
continue
#ip = peer[0].partition('%')[0]
for headline in rsp[1:]:
if not headline:
continue
header, _, value = headline.partition(':')
header = header.strip()
value = value.strip()
if header == 'NT':
peerdata['service'] = value
elif header == 'NTS':
if value == 'ssdp:byebye':
machandlers[mac] = byehandler
elif value == 'ssdp:alive':
machandlers[mac] = handler
r, _, _ = select.select((net4, net6), (), (), 0.1)
for mac in newmacs:
thehandler = machandlers.get(mac, None)
if thehandler:
thehandler(peerbymacaddress[mac])
headline = util.stringify(headline)
headline = headline.partition(':')
if len(headline) < 3:
continue
if headline[0] == 'ST' and headline[-1].startswith(' urn:xcat.org:service:confluent:'):
for query in headline[-1].split('/'):
if query.startswith('uuid='):
curruuid = query.split('=', 1)[1].lower()
node = uuidlookup(curruuid)
if not node:
break
currtime = time.time()
seconds = int(currtime)
msecs = int(currtime * 1000 % 1000)
reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}\r\nCURRTIME: {1}\r\nCURRMSECS: {2}\r\n'.format(node, seconds, msecs)
if not isinstance(reply, bytes):
reply = reply.encode('utf8')
s.sendto(reply, peer)
r, _, _ = select.select((net4, net6), (), (), 0.2)
for mac in newmacs:
thehandler = machandlers.get(mac, None)
if thehandler:
thehandler(peerbymacaddress[mac])
except Exception:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
def _find_service(service, target):
@@ -163,18 +199,26 @@ def _find_service(service, target):
for addr in addrs:
host = addr[4][0]
if addr[0] == socket.AF_INET:
net4.sendto(smsg.format(host, service), addr[4])
msg = smsg.format(host, service)
if not isinstance(msg, bytes):
msg = msg.encode('utf8')
net4.sendto(msg, addr[4])
elif addr[0] == socket.AF_INET6:
host = '[{0}]'.format(host)
net6.sendto(smsg.format(host, service), addr[4])
msg = smsg.format(host, service)
if not isinstance(msg, bytes):
msg = msg.encode('utf8')
net6.sendto(msg, addr[4])
else:
net4.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
for idx in util.list_interface_indexes():
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF,
idx)
try:
net6.sendto(smsg.format('[{0}]'.format(mcastv6addr), service
), (mcastv6addr, 1900, 0, 0))
msg = smsg.format('[{0}]'.format(mcastv6addr), service)
if not isinstance(msg, bytes):
msg = msg.encode('utf8')
net6.sendto(msg, (mcastv6addr, 1900, 0, 0))
except socket.error:
# ignore interfaces without ipv6 multicast causing error
pass
@@ -185,9 +229,14 @@ def _find_service(service, target):
bcast = i4['broadcast']
net4.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
socket.inet_aton(addr))
net4.sendto(smsg.format(mcastv4addr, service),
(mcastv4addr, 1900))
net4.sendto(smsg.format(bcast, service), (bcast, 1900))
msg = smsg.format(mcastv4addr, service)
if not isinstance(msg, bytes):
msg = msg.encode('utf8')
net4.sendto(msg, (mcastv4addr, 1900))
msg = smsg.format(bcast, service)
if not isinstance(msg, bytes):
msg = msg.encode('utf8')
net4.sendto(msg, (bcast, 1900))
# SSDP by spec encourages responses to spread out over a 3 second interval
# hence we must be a bit more patient
deadline = util.monotonic_time() + 4
+5
View File
@@ -30,6 +30,7 @@ import confluent.log as log
import confluent.messages
import confluent.core as pluginapi
import confluent.asynchttp
import confluent.selfservice as selfservice
import confluent.shellserver as shellserver
import confluent.tlvdata
import confluent.util as util
@@ -411,6 +412,10 @@ def resourcehandler_backend(env, start_response):
('X-Permitted-Cross-Domain-Policies', 'none')]
reqbody = None
reqtype = None
if env.get('PATH_INFO', '').startswith('/self/'):
for res in selfservice.handle_request(env, start_response):
yield res
return
if 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0:
reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH']))
reqtype = env['CONTENT_TYPE']
+2
View File
@@ -34,6 +34,7 @@ import confluent.core as confluentcore
import confluent.httpapi as httpapi
import confluent.log as log
import confluent.collective.manager as collective
import confluent.discovery.protocols.pxe as pxe
try:
import confluent.sockapi as sockapi
except ImportError:
@@ -262,6 +263,7 @@ def run(args):
webservice = httpapi.HttpApi(http_bind_host, http_bind_port)
webservice.start()
disco.start_detection()
pxe.start_proxydhcp()
try:
sockservice = sockapi.SockApi(sock_bind_host, sock_bind_port)
sockservice.start()
+5 -1
View File
@@ -1800,8 +1800,12 @@ class CryptedAttributes(Attributes):
nkv = {}
for key in kv:
nkv[key] = {'isset': False}
if 'hashvalue' in kv[key]:
targkey = 'hashvalue'
else:
targkey = 'cryptvalue'
try:
if kv[key] is not None and kv[key]['cryptvalue'] != '':
if kv[key] is not None and kv[key][targkey] != '':
nkv[key] = {'isset': True}
nkv[key]['inheritedfrom'] = kv[key]['inheritedfrom']
except KeyError:
+152 -7
View File
@@ -18,9 +18,11 @@
import confluent.exceptions as exc
import codecs
import netifaces
import struct
import eventlet.green.socket as socket
import eventlet.support.greendns
import os
getaddrinfo = eventlet.support.greendns.getaddrinfo
@@ -38,6 +40,10 @@ def cidr_to_mask(cidr):
socket.AF_INET, struct.pack('!I', (2**32 - 1) ^ (2**(32 - cidr) - 1)))
def ip_on_same_subnet(first, second, prefix):
if first.startswith('::ffff:') and '.' in first:
first = first.replace('::ffff:', '')
if second.startswith('::ffff:') and '.' in second:
second = second.replace('::ffff:', '')
addrinf = socket.getaddrinfo(first, None, 0, socket.SOCK_STREAM)[0]
fam = addrinf[0]
ip = socket.inet_pton(fam, addrinf[-1][0])
@@ -45,7 +51,8 @@ def ip_on_same_subnet(first, second, prefix):
addrinf = socket.getaddrinfo(second, None, 0, socket.SOCK_STREAM)[0]
if fam != addrinf[0]:
return False
oip = socket.inet_pton(fam, addrinf[-1][0])
txtaddr = addrinf[-1][0].split('%')[0]
oip = socket.inet_pton(fam, txtaddr)
oip = int(codecs.encode(bytes(oip), 'hex'), 16)
if fam == socket.AF_INET:
addrlen = 32
@@ -57,6 +64,64 @@ def ip_on_same_subnet(first, second, prefix):
return ip & mask == oip & mask
def address_is_local(address):
for iface in netifaces.interfaces():
for i4 in netifaces.ifaddresses(iface).get(2, []):
cidr = mask_to_cidr(i4['netmask'])
if ip_on_same_subnet(i4['addr'], address, cidr):
return True
for i6 in netifaces.ifaddresses(iface).get(10, []):
cidr = int(i6['netmask'].split('/')[1])
laddr = i6['addr'].split('%')[0]
if ip_on_same_subnet(laddr, address, cidr):
return True
return False
_idxtoifnamemap = {}
def _rebuildidxmap():
_idxtoifnamemap.clear()
for iname in os.listdir('/sys/class/net'):
ci = int(open('/sys/class/net/{0}/ifindex'.format(iname)).read())
_idxtoifnamemap[ci] = iname
def myiptonets(svrip):
fam = netifaces.AF_INET
if ':' in svrip:
fam = netifaces.AF_INET6
relevantnic = None
for iface in netifaces.interfaces():
for addr in netifaces.ifaddresses(iface).get(fam, []):
addr = addr.get('addr', '')
addr = addr.split('%')[0]
if addresses_match(addr, svrip):
relevantnic = iface
break
else:
continue
break
return inametonets(relevantnic)
def idxtonets(ifidx):
_rebuildidxmap()
iname = _idxtoifnamemap.get(ifidx, None)
return inametonets(iname)
def inametonets(iname):
addrs = netifaces.ifaddresses(iname)
try:
addrs = addrs[netifaces.AF_INET]
except KeyError:
return
for addr in addrs:
ip = struct.unpack('!I', socket.inet_aton(addr['addr']))[0]
mask = struct.unpack('!I', socket.inet_aton(addr['netmask']))[0]
net = ip & mask
net = socket.inet_ntoa(struct.pack('!I', net))
yield (net, mask_to_cidr(addr['netmask']), addr['addr'])
# TODO(jjohnson2): have a method to arbitrate setting methods, to aid
# in correct matching of net.* based on parameters, mainly for pxe
# The scheme for pxe:
@@ -66,19 +131,21 @@ def ip_on_same_subnet(first, second, prefix):
# that mac address
# the ip as reported by recvmsg to match the subnet of that net.* interface
# if switch and port available, that should match.
def get_nic_config(configmanager, node, ip=None, mac=None):
def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
serverip=None):
"""Fetch network configuration parameters for a nic
For a given node and interface, find and retrieve the pertinent network
configuration data. The desired configuration can be searched
either by ip or by mac.
:param configmanager: The relevant confluent.config.ConfigManager
:param configmanager: The relevant confluent.config.ConfigManager
instance.
:param node: The name of the node
:param ip: An IP address on the intended subnet
:param mac: The mac address of the interface
:param ifidx: The local index relevant to the network.
:returns: A dict of parameters, 'ipv4_gateway', ....
"""
# ip parameter *could* be the result of recvmsg with cmsg to tell
@@ -87,15 +154,93 @@ def get_nic_config(configmanager, node, ip=None, mac=None):
# join a bond/bridge, vlan configs, etc.
# also other nic criteria, physical location, driver and index...
nodenetattribs = configmanager.get_node_attributes(
node, 'net*.ipv4_gateway').get(node, {})
node, 'net*').get(node, {})
cfgbyname = {}
for attrib in nodenetattribs:
segs = attrib.split('.')
if len(segs) == 2:
name = None
else:
name = segs[1]
if name not in cfgbyname:
cfgbyname[name] = {}
cfgbyname[name][segs[-1]] = nodenetattribs[attrib].get('value',
None)
cfgdata = {
'ipv4_gateway': None,
'ipv4_address': None,
'ipv4_method': None,
'prefix': None,
}
nets = None
needsvrip = False
if ifidx is not None:
dhcprequested = False
nets = list(idxtonets(ifidx))
if not nets:
cfgdata['ipv4_broken'] = True
if serverip is not None:
needsvrip = True
dhcprequested = False
nets = list(myiptonets(serverip))
if nets is not None:
candgws = []
candsrvs = []
for net in nets:
net, prefix, svrip = net
candsrvs.append(svrip)
cfgdata['deploy_server'] = svrip
for candidate in cfgbyname:
if cfgbyname[candidate].get('ipv4_method', None) == 'dhcp':
dhcprequested = True
continue
candip = cfgbyname[candidate].get('ipv4_address', None)
if candip and '/' in candip:
candip, candprefix = candip.split('/')
if int(candprefix) != prefix:
continue
candgw = cfgbyname[candidate].get('ipv4_gateway', None)
if candip:
if ip_on_same_subnet(net, candip, prefix):
cfgdata['ipv4_address'] = candip
cfgdata['ipv4_method'] = 'static'
cfgdata['ipv4_gateway'] = cfgbyname[candidate].get(
'ipv4_gateway', None)
cfgdata['prefix'] = prefix
return cfgdata
elif candgw:
if ip_on_same_subnet(net, candgw, prefix):
candgws.append(candgw)
if dhcprequested:
return cfgdata
ipbynodename = None
try:
ipbynodename = socket.getaddrinfo(
node, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0]
except Exception:
return cfgdata
for net in nets:
net, prefix, svrip = net
if ip_on_same_subnet(net, ipbynodename, prefix):
cfgdata['ipv4_address'] = ipbynodename
cfgdata['ipv4_method'] = 'static'
cfgdata['prefix'] = prefix
break
for svr in candsrvs:
if ip_on_same_subnet(svr, ipbynodename, prefix):
cfgdata['deploy_server'] = svr
break
for gw in candgws:
if ip_on_same_subnet(gw, ipbynodename, prefix):
cfgdata['ipv4_gateway'] = gw
break
return cfgdata
if ip is not None:
prefixlen = get_prefix_len_for_ip(ip)
cfgdata['prefix'] = prefixlen
for setting in nodenetattribs:
if 'ipv4_gateway' not in setting:
continue
gw = nodenetattribs[setting].get('value', None)
if gw is None or not gw:
continue
+457
View File
@@ -0,0 +1,457 @@
#!/usr/bin/python
import confluent.messages as msg
import eventlet
import eventlet.green.select as select
import eventlet.green.subprocess as subprocess
import glob
import logging
logging.getLogger('libarchive').addHandler(logging.NullHandler())
import libarchive
import hashlib
import os
import shutil
import sys
import time
import yaml
COPY = 1
EXTRACT = 2
READFILES = set([
'README.diskdefines',
'media.1/products',
'media.2/products',
'.discinfo',
])
HEADERSUMS = set([b'\x85\xeddW\x86\xc5\xbdhx\xbe\x81\x18X\x1e\xb4O\x14\x9d\x11\xb7C8\x9b\x97R\x0c-\xb8Ht\xcb\xb3'])
HASHPRINTS = {
'69d5f1c5e4474d70b0fb5374bfcb29bf57ba828ff00a55237cd757e61ed71048': {'name': 'cumulus-broadcom-amd64-4.0.0', 'method': COPY},
}
from ctypes import byref, c_longlong, c_size_t, c_void_p
from libarchive.ffi import (
write_disk_new, write_disk_set_options, write_free, write_header,
read_data_block, write_data_block, write_finish_entry, ARCHIVE_EOF
)
def relax_umask():
os.umask(0o22)
def update_boot(profiledir):
profile = {}
if profiledir.endswith('/'):
profiledir = profiledir[:-1]
profname = os.path.basename(profiledir)
with open('{0}/profile.yaml'.format(profiledir)) as profileinfo:
profile = yaml.safe_load(profileinfo)
label = profile.get('label', profname)
kernelargs = profile.get('kernelargs', '')
grubcfg = "set timeout=5\nmenuentry '"
grubcfg += label
grubcfg += "' {\n linuxefi /kernel " + kernelargs + "\n"
initrds = []
for initramfs in glob.glob(profiledir + '/boot/initramfs/*.cpio'):
initramfs = os.path.basename(initramfs)
initrds.append(initramfs)
for initramfs in os.listdir(profiledir + '/boot/initramfs'):
if initramfs not in initrds:
initrds.append(initramfs)
grubcfg += " initrdefi "
for initramfs in initrds:
grubcfg += " /initramfs/{0}".format(initramfs)
grubcfg += "\n}\n"
with open(profiledir + '/boot/efi/boot/grub.cfg', 'w') as grubout:
grubout.write(grubcfg)
ipxeargs = kernelargs
for initramfs in initrds:
ipxeargs += " initrd=" + initramfs
oum = os.umask(0o22)
ipout = os.open(profiledir + '/boot/boot.ipxe', os.O_WRONLY|os.O_CREAT, 0o644)
ipxeout = os.fdopen(ipout, 'w')
try:
os.umask(oum)
ipxeout.write('#!ipxe\n')
ipxeout.write('imgfetch kernel ' + ipxeargs + '\n')
for initramfs in initrds:
ipxeout.write('imgfetch initramfs/{0}\n'.format(initramfs))
ipxeout.write('imgload kernel\nimgexec kernel\n')
finally:
ipxeout.close()
subprocess.check_call(
['/opt/confluent/bin/dir2img', '{0}/boot'.format(profiledir),
'{0}/boot.img'.format(profiledir)], preexec_fn=relax_umask)
def extract_entries(entries, flags=0, callback=None, totalsize=None, extractlist=None):
"""Extracts the given archive entries into the current directory.
"""
buff, size, offset = c_void_p(), c_size_t(), c_longlong()
buff_p, size_p, offset_p = byref(buff), byref(size), byref(offset)
sizedone = 0
printat = 0
with libarchive.extract.new_archive_write_disk(flags) as write_p:
for entry in entries:
if str(entry).endswith('TRANS.TBL'):
continue
if extractlist and str(entry) not in extractlist:
continue
write_header(write_p, entry._entry_p)
read_p = entry._archive_p
while 1:
r = read_data_block(read_p, buff_p, size_p, offset_p)
sizedone += size.value
if callback and time.time() > printat:
callback({'progress': float(sizedone) / float(totalsize)})
printat = time.time() + 0.5
if r == ARCHIVE_EOF:
break
write_data_block(write_p, buff, size, offset)
write_finish_entry(write_p)
if callback:
callback({'progress': float(sizedone) / float(totalsize)})
def extract_file(filepath, flags=0, callback=lambda x: None, imginfo=(), extractlist=None):
"""Extracts an archive from a file into the current directory."""
totalsize = 0
for img in imginfo:
if not imginfo[img]:
continue
totalsize += imginfo[img]
with libarchive.file_reader(filepath) as archive:
extract_entries(archive, flags, callback, totalsize, extractlist)
def check_centos(isoinfo):
ver = None
arch = None
cat = None
for entry in isoinfo[0]:
if 'centos-release-7' in entry:
dotsplit = entry.split('.')
arch = dotsplit[-2]
ver = dotsplit[0].split('release-')[-1].replace('-', '.')
cat = 'el7'
break
elif 'centos-release-8' in entry:
ver = entry.split('-')[2]
arch = entry.split('.')[-2]
cat = 'el8'
break
else:
return None
return {'name': 'centos-{0}-{1}'.format(ver, arch), 'method': EXTRACT, 'category': cat}
def check_ubuntu(isoinfo):
if 'README.diskdefines' not in isoinfo[1]:
return None
arch = None
variant = None
ver = None
diskdefs = isoinfo[1]['README.diskdefines']
for info in diskdefs.split(b'\n'):
if not info:
continue
_, key, val = info.split(b' ', 2)
val = val.strip()
if key == b'ARCH':
arch = val
if arch == b'amd64':
arch = b'x86_64'
elif key == b'DISKNAME':
variant, ver, _ = val.split(b' ', 2)
if variant != b'Ubuntu-Server':
return None
if variant:
if not isinstance(ver, str):
ver = ver.decode('utf8')
if not isinstance(arch, str):
arch = arch.decode('utf8')
major = '.'.join(ver.split('.', 2)[:2])
return {'name': 'ubuntu-{0}-{1}'.format(ver, arch),
'method': EXTRACT|COPY,
'extractlist': ['casper/vmlinuz', 'casper/initrd',
'EFI/BOOT/BOOTx64.EFI', 'EFI/BOOT/grubx64.efi'
],
'copyto': 'install.iso',
'category': 'ubuntu{0}'.format(major)}
def check_sles(isoinfo):
ver = None
arch = 'x86_64'
disk = None
distro = ''
if 'media.1/products' in isoinfo[1]:
medianame = 'media.1/products'
elif 'media.2/products' in isoinfo[1]:
medianame = 'media.2/products'
else:
return None
prodinfo = isoinfo[1][medianame]
if not isinstance(prodinfo, str):
prodinfo = prodinfo.decode('utf8')
prodinfo = prodinfo.split('\n')
hline = prodinfo[0].split(' ')
ver = hline[-1].split('-')[0]
major = ver.split('.', 2)[0]
if hline[-1].startswith('15'):
if hline[1] == 'openSUSE-Leap':
distro = 'opensuse_leap'
else:
distro = 'sle'
if hline[0] == '/' or 'boot' in isoinfo[0]:
disk = '1'
elif hline[0].startswith('/Module'):
disk = '2'
elif hline[-1].startswith('12'):
if 'SLES' in hline[1]:
distro = 'sles'
if '.1' in medianame:
disk = '1'
elif '.2' in medianame:
disk = '2'
if disk and distro:
return {'name': '{0}-{1}-{2}'.format(distro, ver, arch),
'method': EXTRACT, 'subname': disk,
'category': 'suse{0}'.format(major)}
return None
def check_rhel(isoinfo):
ver = None
arch = None
for entry in isoinfo[0]:
if 'redhat-release-7' in entry:
dotsplit = entry.split('.')
arch = dotsplit[-2]
ver = dotsplit[0].split('release-')[-1].replace('-', '.')
break
elif 'redhat-release-8' in entry:
ver = entry.split('-')[2]
arch = entry.split('.')[-2]
break
else:
return None
major = ver.split('.', 1)[0]
return {'name': 'rhel-{0}-{1}'.format(ver, arch), 'method': EXTRACT, 'category': 'el{0}'.format(major)}
def scan_iso(filename):
filesizes = {}
filecontents = {}
with libarchive.file_reader(filename) as reader:
for ent in reader:
if str(ent).endswith('TRANS.TBL'):
continue
eventlet.sleep(0)
filesizes[str(ent)] = ent.size
if str(ent) in READFILES:
filecontents[str(ent)] = b''
for block in ent.get_blocks():
filecontents[str(ent)] += bytes(block)
return filesizes, filecontents
def fingerprint(filename):
with open(filename, 'rb') as archive:
header = archive.read(32768)
archive.seek(32769)
if archive.read(6) == b'CD001\x01':
# ISO image
isoinfo = scan_iso(filename)
name = None
for fun in globals():
if fun.startswith('check_'):
name = globals()[fun](isoinfo)
if name:
return name, isoinfo[0]
return None
else:
sum = hashlib.sha256(header)
if sum.digest() in HEADERSUMS:
archive.seek(32768)
chunk = archive.read(32768)
while chunk:
sum.update(chunk)
chunk = archive.read(32768)
imginfo = HASHPRINTS.get(sum.hexdigest(), None)
if imginfo:
return imginfo, None
def import_image(filename, callback, backend=False):
identity = fingerprint(filename)
if not identity:
return -1
identity, imginfo = identity
targpath = identity['name']
if identity.get('subname', None):
targpath += '/' + identity['subname']
targpath = '/var/lib/confluent/distributions/' + targpath
os.makedirs(targpath, 0o755)
filename = os.path.abspath(filename)
os.chdir(targpath)
if not backend:
print('Importing OS to ' + targpath + ':')
printit({'progress': 0.0})
if EXTRACT & identity['method']:
extract_file(filename, callback=callback, imginfo=imginfo, extractlist=identity.get('extractlist', None))
if COPY & identity['method']:
basename = identity.get('copyto', os.path.basename(filename))
targpath = os.path.join(targpath, basename)
shutil.copyfile(filename, targpath)
printit({'progress': 1.0})
sys.stdout.write('\n')
def printit(info):
sys.stdout.write(' \r{:.2f}%'.format(100 * info['progress']))
sys.stdout.flush()
def list_distros():
return os.listdir('/var/lib/confluent/distributions')
def list_profiles():
return os.listdir('/var/lib/confluent/public/os/')
def get_profile_label(profile):
with open('/var/lib/confluent/public/os/{0}/profile.yaml') as metadata:
prof = yaml.safe_load(metadata)
return prof.get('label', profile)
importing = {}
class MediaImporter(object):
def __init__(self, media):
self.worker = None
self.profiles = []
identity = fingerprint(media)
self.percent = 0.0
identity, _ = identity
self.phase = 'copying'
if not identity:
raise Exception('Unrecognized OS Media')
if 'subname' in identity:
importkey = '{0}-{1}'.format(identity['name'], identity['subname'])
else:
importkey = identity['name']
if importkey in importing:
raise Exception('Media import already in progress for this media')
self.importkey = importkey
importing[importkey] = self
self.importkey = importkey
self.osname = identity['name']
self.oscategory = identity.get('category', None)
targpath = identity['name']
self.distpath = '/var/lib/confluent/distributions/' + targpath
if identity.get('subname', None):
targpath += '/' + identity['subname']
self.targpath = '/var/lib/confluent/distributions/' + targpath
if os.path.exists(self.targpath):
raise Exception('{0} already exists'.format(self.targpath))
self.filename = os.path.abspath(media)
self.importer = eventlet.spawn(self.importmedia)
def stop(self):
if self.worker and self.worker.poll() is None:
self.worker.kill()
@property
def progress(self):
return {'phase': self.phase, 'progress': self.percent, 'profiles': self.profiles}
def importmedia(self):
os.environ['PYTHONPATH'] = ':'.join(sys.path)
with open(os.devnull, 'w') as devnull:
self.worker = subprocess.Popen(
[sys.executable, __file__, self.filename, '-b'],
stdin=devnull, stdout=subprocess.PIPE)
wkr = self.worker
currline = b''
while wkr.poll() is None:
currline += wkr.stdout.read(1)
if b'\r' in currline:
val = currline.split(b'%')[0].strip()
if val:
self.percent = float(val)
currline = b''
a = wkr.stdout.read(1)
while a:
currline += a
if b'\r' in currline:
val = currline.split(b'%')[0].strip()
if val:
self.percent = float(val)
currline = b''
a = wkr.stdout.read(1)
bootupdates = []
if self.oscategory:
defprofile = '/opt/confluent/lib/osdeploy/{0}'.format(
self.oscategory)
osd, osversion, arch = self.osname.split('-')
for prof in os.listdir('{0}/profiles'.format(defprofile)):
srcname = '{0}/profiles/{1}'.format(defprofile, prof)
profname = '{0}-{1}'.format(self.osname, prof)
dirname = '/var/lib/confluent/public/os/{0}'.format(profname)
if os.path.exists(dirname):
continue
oumask = os.umask(0o22)
shutil.copytree(srcname, dirname)
profdata = None
try:
os.makedirs('{0}/boot/initramfs'.format(dirname), 0o755)
except OSError as e:
if e.errno != 17:
raise
finally:
os.umask(oumask)
with open('{0}/profile.yaml'.format(dirname)) as yin:
profdata = yin.read()
profdata = profdata.replace('%%DISTRO%%', osd)
profdata = profdata.replace('%%VERSION%%', osversion)
profdata = profdata.replace('%%ARCH%%', arch)
profdata = profdata.replace('%%PROFILE%%', profname)
if profdata:
with open('{0}/profile.yaml'.format(dirname), 'w') as yout:
yout.write(profdata)
for initrd in os.listdir('{0}/initramfs'.format(defprofile)):
fullpath = '{0}/initramfs/{1}'.format(defprofile, initrd)
os.symlink(fullpath, '{0}/boot/initramfs/{1}'.format(dirname, initrd))
os.symlink(
'/var/lib/confluent/public/site/initramfs.cpio',
'{0}/boot/initramfs/site.cpio'.format(dirname))
os.symlink(self.distpath, '{0}/distribution'.format(dirname))
subprocess.check_call(
['sh', '{0}/initprofile.sh'.format(dirname),
self.targpath, dirname])
bootupdates.append(eventlet.spawn(update_boot, dirname))
self.profiles.append(profname)
for upd in bootupdates:
upd.wait()
self.phase = 'complete'
self.percent = 100.0
def list_importing():
return [msg.ChildCollection(x) for x in importing]
def remove_importing(importkey):
importing[importkey].stop()
del importing[importkey]
yield msg.DeletedResource('deployment/importing/{0}'.format(importkey))
def get_importing_status(importkey):
yield msg.KeyValueData(importing[importkey].progress)
if __name__ == '__main__':
os.umask(0o022)
if len(sys.argv) > 2:
sys.exit(import_image(sys.argv[1], callback=printit, backend=True))
else:
sys.exit(import_image(sys.argv[1], callback=printit))
@@ -57,7 +57,7 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
val['desc'] = 'The noderange this group is expanded ' \
'to when used in noderange, exclusive with static ' \
'nodes'
if attribute.startswith('secret.'):
if attribute.startswith('secret.') or attribute.startswith('crypted.'):
yield msg.CryptedAttributes(
kv={attribute: val},
desc=allattributes.node[attribute]['description'])
@@ -117,7 +117,7 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
val = []
else: # no setting, provide a blank
val = {'value': None}
if attribute.startswith('secret.'):
if attribute.startswith('secret.') or attribute.startswith('crypted.'):
yield msg.CryptedAttributes(
node, {attribute: val},
allattributes.node.get(
@@ -142,7 +142,7 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
desc = ''
if 'value' in currattr or 'expression' in currattr:
yield msg.Attributes(node, {attribute: currattr}, desc)
elif 'cryptvalue' in currattr:
elif 'cryptvalue' in currattr or 'hashvalue' in currattr:
yield msg.CryptedAttributes(
node, {attribute: currattr}, desc)
elif isinstance(currattr, list):
+170
View File
@@ -0,0 +1,170 @@
import confluent.config.configmanager as configmanager
import confluent.collective.manager as collective
import confluent.netutil as netutil
import confluent.sshutil as sshutil
import confluent.util as util
import eventlet.green.subprocess as subprocess
import crypt
import json
import time
import yaml
currtz = None
keymap = 'us'
currlocale = 'en_US.UTF-8'
currtzvintage = None
def yamldump(input):
return yaml.safe_dump(input, default_flow_style=False)
def handle_request(env, start_response):
global currtz
global keymap
global currlocale
global currtzvintage
nodename = env.get('HTTP_CONFLUENT_NODENAME', None)
apikey = env.get('HTTP_CONFLUENT_APIKEY', None)
if not (nodename and apikey):
start_response('401 Unauthorized', [])
yield 'Unauthorized'
return
cfg = configmanager.ConfigManager(None)
eak = cfg.get_node_attributes(nodename, 'crypted.selfapikey').get(
nodename, {}).get('crypted.selfapikey', {}).get('hashvalue', None)
if not eak:
start_response('401 Unauthorized', [])
yield 'Unauthorized'
return
salt = '$'.join(eak.split('$', 3)[:-1]) + '$'
if crypt.crypt(apikey, salt) != eak:
start_response('401 Unauthorized', [])
yield 'Unauthorized'
return
retype = env.get('HTTP_ACCEPT', 'application/yaml')
isgeneric = False
if retype == '*/*':
isgeneric = True
retype = 'application/yaml'
if retype == 'application/yaml':
dumper = yamldump
elif retype == 'application/json':
dumper = json.dumps
else:
start_response('406 Not supported', [])
yield 'Unsupported content type in ACCEPT: ' + retype
return
if env['REQUEST_METHOD'] not in ('HEAD', 'GET') and 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0:
reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH']))
if env['PATH_INFO'] == '/self/deploycfg':
myip = env.get('HTTP_X_FORWARDED_HOST', None)
myip = myip.replace('[', '').replace(']', '')
ncfg = netutil.get_nic_config(cfg, nodename, serverip=myip)
if ncfg['prefix']:
ncfg['ipv4_netmask'] = netutil.cidr_to_mask(ncfg['prefix'])
deployinfo = cfg.get_node_attributes(
nodename, ('deployment.*', 'console.method', 'crypted.rootpassword',
'dns.*'))
deployinfo = deployinfo.get(nodename, {})
profile = deployinfo.get(
'deployment.pendingprofile', {}).get('value', '')
ncfg['profile'] = profile
protocol = deployinfo.get('deployment.useinsecureprotocols', {}).get(
'value', 'never')
ncfg['textconsole'] = bool(deployinfo.get(
'console.method', {}).get('value', None))
if protocol == 'always':
ncfg['protocol'] = 'http'
else:
ncfg['protocol'] = 'https'
ncfg['rootpassword'] = deployinfo.get('crypted.rootpassword', {}).get(
'hashvalue', None)
if currtzvintage and currtzvintage > (time.time() - 30.0):
ncfg['timezone'] = currtz
else:
langinfo = subprocess.check_output(
['localectl', 'status']).split(b'\n')
for line in langinfo:
line = line.strip()
if line.startswith(b'System Locale:'):
ccurrlocale = line.split(b'=')[-1]
if not ccurrlocale:
continue
if not isinstance(ccurrlocale, str):
ccurrlocale = ccurrlocale.decode('utf8')
if ccurrlocale == 'n/a':
continue
currlocale = ccurrlocale
elif line.startswith(b'VC Keymap:'):
ckeymap = line.split(b':')[-1]
ckeymap = ckeymap.strip()
if not ckeymap:
continue
if not isinstance(ckeymap, str):
ckeymap = ckeymap.decode('utf8')
if ckeymap == 'n/a':
continue
keymap = ckeymap
tdc = subprocess.check_output(['timedatectl']).split(b'\n')
for ent in tdc:
ent = ent.strip()
if ent.startswith(b'Time zone:'):
currtz = ent.split(b': ', 1)[1].split(b'(', 1)[0].strip()
if not isinstance(currtz, str):
currtz = currtz.decode('utf8')
currtzvintage = time.time()
ncfg['timezone'] = currtz
break
ncfg['locale'] = currlocale
ncfg['keymap'] = keymap
ncfg['nameservers'] = []
for dns in deployinfo.get(
'dns.servers', {}).get('value', '').split(','):
ncfg['nameservers'].append(dns)
dnsdomain = deployinfo.get('dns.domain', {}).get('value', None)
ncfg['dnsdomain'] = dnsdomain
start_response('200 OK', (('Content-Type', retype),))
yield dumper(ncfg)
elif env['PATH_INFO'] == '/self/sshcert':
if not sshutil.ca_exists():
start_response('500 Unconfigured', ())
yield 'CA is not configured on this system (run ...)'
return
cert = sshutil.sign_host_key(reqbody, nodename)
start_response('200 OK', (('Content-Type', 'text/plain'),))
yield cert
elif env['PATH_INFO'] == '/self/nodelist':
nodes = set(cfg.list_nodes())
for mgr in configmanager.list_collective():
nodes.add(mgr)
nodes.add(collective.get_myname())
if isgeneric:
start_response('200 OK', (('Content-Type', 'text/plain'),))
for node in util.natural_sort(nodes):
yield node + '\n'
else:
start_response('200 OK', (('Content-Type', retype),))
yield dumper(sorted(nodes))
elif env['PATH_INFO'] == '/self/updatestatus':
update = yaml.safe_load(reqbody)
if update['status'] != 'complete':
raise Exception('Unknown update status request')
currattr = cfg.get_node_attributes(nodename, 'deployment.*').get(
nodename, {})
pending = currattr.get('deployment.pendingprofile', {}).get('value', '')
updates = {}
if pending:
updates['deployment.pendingprofile'] = {'value': ''}
currprof = currattr.get('deployment.profile', {}).get('value', '')
if currprof != pending:
updates['deployment.profile'] = {'value': pending}
cfg.set_node_attributes({nodename: updates})
start_response('200 OK', (('Content-Type', 'text/plain'),))
yield 'OK'
else:
start_response('500 Error', (('Content-Type', 'text/plain'),))
yield 'No pending profile detected, unable to accept status update'
else:
start_response('404 Not Found', ())
yield 'Not found'
+8 -2
View File
@@ -37,6 +37,7 @@ import eventlet.green.ssl as ssl
import eventlet
import confluent.auth as auth
import confluent.credserver as credserver
import confluent.tlvdata as tlvdata
import confluent.consoleserver as consoleserver
import confluent.config.configmanager as configmanager
@@ -352,9 +353,13 @@ def _tlshandler(bind_host, bind_port):
# Enable TCP_FASTOPEN
plainsocket.setsockopt(socket.SOL_TCP, 23, 5)
plainsocket.listen(5)
cs = credserver.CredServer()
while (1): # TODO: exithook
cnn, addr = plainsocket.accept()
eventlet.spawn_n(_tlsstartup, cnn)
if addr[1] < 1000:
eventlet.spawn_n(cs.handle_client, cnn, addr)
else:
eventlet.spawn_n(_tlsstartup, cnn)
if ffi:
@@ -473,9 +478,10 @@ class SockApi(object):
def watch_for_cert(self):
libc = ctypes.CDLL(ctypes.util.find_library('c'))
watcher = libc.inotify_init()
if libc.inotify_add_watch(watcher, '/etc/confluent/', 0x100) > -1:
if libc.inotify_add_watch(watcher, b'/etc/confluent/', 0x100) > -1:
while True:
select.select((watcher,), (), (), 86400)
os.read(watcher, 1024)
if self.should_run_remoteapi():
os.close(watcher)
self.start_remoteapi()
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/python
import confluent.collective.manager as collective
import eventlet.green.subprocess as subprocess
import glob
import os
import shutil
import tempfile
def normalize_uid():
curruid = os.geteuid()
neededuid = os.stat('/etc/confluent').st_uid
if curruid != neededuid:
os.seteuid(neededuid)
if os.geteuid() != neededuid:
raise Exception('Need to run as root or owner of /etc/confluent')
return curruid
def initialize_ca():
ouid = normalize_uid()
try:
os.makedirs('/etc/confluent/ssh', mode=0o700)
except OSError as e:
if e.errno != 17:
raise
finally:
os.seteuid(ouid)
myname = collective.get_myname()
caname = '{0} SSH CA'.format(myname)
subprocess.check_call(
['ssh-keygen', '-C', caname, '-t', 'ed25519', '-f',
'/etc/confluent/ssh/ca', '-N', ''], preexec_fn=normalize_uid)
try:
os.makedirs('/var/lib/confluent/public/site/ssh/', mode=0o755)
except OSError as e:
if e.errno != 17:
raise
cafilename = '/var/lib/confluent/public/site/ssh/{0}.ca'.format(myname)
shutil.copy('/etc/confluent/ssh/ca.pub', cafilename)
# newent = '@cert-authority * ' + capub.read()
def sign_host_key(pubkey, nodename):
tmpdir = tempfile.mkdtemp()
try:
pkeyname = os.path.join(tmpdir, 'hostkey.pub')
with open(pkeyname, 'wb') as pubfile:
pubfile.write(pubkey)
subprocess.check_call(
['ssh-keygen', '-s', '/etc/confluent/ssh/ca', '-I', nodename,
'-n', nodename, '-h', pkeyname])
certname = pkeyname.replace('.pub', '-cert.pub')
with open(certname) as cert:
return cert.read()
finally:
shutil.rmtree(tmpdir)
def initialize_root_key(generate):
authorized = []
myname = collective.get_myname()
for currkey in glob.glob('/root/.ssh/*.pub'):
authorized.append(currkey)
if generate and not authorized:
subprocess.check_call(['ssh-keygen', '-t', 'ed25519', '-f', '/root/.ssh/id_ed25519', '-N', ''])
for currkey in glob.glob('/root/.ssh/*.pub'):
authorized.append(currkey)
try:
os.makedirs('/var/lib/confluent/public/site/ssh', mode=0o755)
neededuid = os.stat('/etc/confluent').st_uid
os.chown('/var/lib/confluent', neededuid, -1)
os.chown('/var/lib/confluent/public', neededuid, -1)
os.chown('/var/lib/confluent/public/site', neededuid, -1)
os.chown('/var/lib/confluent/public/site/ssh', neededuid, -1)
except OSError as e:
if e.errno != 17:
raise
neededuid = os.stat('/etc/confluent').st_uid
for auth in authorized:
shutil.copy(
auth,
'/var/lib/confluent/public/site/ssh/{0}.rootpubkey'.format(
myname))
os.chmod('/var/lib/confluent/public/site/ssh/{0}.rootpubkey'.format(
myname), 0o644)
os.chown('/var/lib/confluent/public/site/ssh/{0}.rootpubkey'.format(
myname), neededuid, -1)
def ca_exists():
return os.path.exists('/etc/confluent/ssh/ca')
if __name__ == '__main__':
initialize_root_key(True)
if not ca_exists():
initialize_ca()
print(repr(sign_host_key(open('/etc/ssh/ssh_host_ed25519_key.pub').read(), collective.get_myname())))
+3 -3
View File
@@ -13,7 +13,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
%if "%{dist}" == ".el8"
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic, python3-websocket-client python3-msgpack
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic, python3-websocket-client python3-msgpack python3-libarchive-c
%else
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil, python2-websocket-client python2-msgpack
%endif
@@ -58,7 +58,7 @@ true
getent group confluent > /dev/null || /usr/sbin/groupadd -r confluent
getent passwd confluent > /dev/null || /usr/sbin/useradd -r -g confluent -d /var/lib/confluent -s /sbin/nologin confluent
mkdir -p /etc/confluent /var/lib/confluent /var/log/confluent /var/cache/confluent
chown -R confluent:confluent /etc/confluent /var/lib/confluent /var/log/confluent /var/cache/confluent
chown confluent:confluent /etc/confluent /var/log/confluent /var/cache/confluent
%post
sysctl -p /usr/lib/sysctl.d/confluent.conf >& /dev/null
@@ -73,7 +73,7 @@ if [ $NEEDCHOWN = 1 ]; then
NEEDSTART=1
systemctl stop confluent
fi
chown -R confluent:confluent /etc/confluent /var/lib/confluent /var/log/confluent /var/cache/confluent
chown -R confluent:confluent /etc/confluent /var/log/confluent /var/cache/confluent
fi
systemctl daemon-reload
if systemctl is-active confluent > /dev/null || [ $NEEDSTART = 1 ]; then /usr/bin/systemctl restart confluent >& /dev/null; fi
+1 -1
View File
@@ -21,7 +21,7 @@ setup(
install_requires=['paramiko', 'pycrypto>=2.6', 'confluent_client>=0.1.0', 'eventlet',
'dnspython', 'netifaces', 'pyte', 'pysnmp', 'pyparsing',
'pyghmi>=1.0.44'],
scripts=['bin/confluent', 'bin/confluentdbutil', 'bin/collective'],
scripts=['bin/confluent', 'bin/confluentdbutil', 'bin/collective', 'bin/osimage'],
data_files=[('/etc/init.d', ['sysvinit/confluent']),
('/usr/lib/sysctl.d', ['sysctl/confluent.conf']),
('/usr/lib/systemd/system', ['systemd/confluent.service']),
+1 -1
View File
@@ -14,7 +14,7 @@ ConfigurationDirectory=confluent
ExecStart=/opt/confluent/bin/confluent
ExecStop=/opt/confluent/bin/confetty shutdown /
Restart=on-failure
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_CHOWN
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_CHOWN CAP_NET_RAW
User=confluent
Group=confluent
DevicePolicy=closed
-85
View File
@@ -1,85 +0,0 @@
/* Copyright 2019 Lenovo */
#include <arpa/inet.h>
#include <crypt.h>
#include <net/if.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define OUI_ETHERTYPE 0x88b7
#define MAXPACKET 1024
#define CHDR "\xa4\x8c\xdb\x30\x01"
int get_interface_index(int sock, char *interface) {
struct ifreq req;
memset(&req, 0, sizeof(req));
strncpy(req.ifr_name, interface, IFNAMSIZ);
if (ioctl(sock, SIOCGIFINDEX, &req) < 0) {
return -1;
}
return req.ifr_ifindex;
}
unsigned char* genpasswd() {
unsigned char * passwd;
int urandom;
passwd = calloc(33, sizeof(char));
urandom = open("/dev/urandom", O_RDONLY);
read(urandom, passwd, 32);
close(urandom);
for (urandom = 0; urandom < 32; urandom++) {
passwd[urandom] = 0x30 + (passwd[urandom] >> 2);
}
return passwd;
}
int parse_macaddr(char* macaddr) {
unsigned char *curr;
unsigned char idx;
curr = strtok(macaddr, ":-");
idx = 0;
while (curr != NULL) {
macaddr[idx++] = strtoul(curr, NULL, 16);
curr = strtok(NULL, ":-");
}
}
int main(int argc, char* argv[]) {
int sock;
int iface;
unsigned char* passwd;
unsigned char* macaddr;
unsigned char buffer[MAXPACKET];
passwd = genpasswd();
if (argc < 3) {
fprintf(stderr, "Missing interface name and target MAC\n");
exit(1);
}
printf("%s\n", argv[2]);
parse_macaddr(argv[2]);
printf("%s\n", argv[2]);
sock = socket(AF_PACKET, SOCK_DGRAM, htons(OUI_ETHERTYPE));
if (sock < 0) {
fprintf(stderr, "Unable to open socket (run as root?)\n");
exit(1);
}
iface = get_interface_index(sock, argv[1]);
if (iface < 0) {
fprintf(stderr, "Unable to find specified interface '%s'\n", argv[1]);
exit(1);
}
}
+46
View File
@@ -0,0 +1,46 @@
import argparse
import io
import gzip
import pyghmi.redfish.command as cmd
import os
import sys
ap = argparse.ArgumentParser(description='Certificate Generate')
ap.add_argument('xcc', help='XCC address')
ap.add_argument('--country', help='Two Letter Country')
ap.add_argument('--state', help='State or Province')
ap.add_argument('--city', help='City or Locality')
ap.add_argument('--org', help='Organization name')
ap.add_argument('--name', help='Common/Host Name')
args = ap.parse_args()
c = cmd.Command(args.xcc, os.environ['XCCUSER'], os.environ['XCCPASS'],
verifycallback=lambda x: True)
params = [
'0', # 'serviceType'
args.country,
args.state,
args.city,
args.org,
args.name,
'',
'',
'',
'',
'',
'',
'',
'',
'',
]
wc = c.oem.wc
rsp, status = wc.grab_json_response_with_status('/api/function', {'Sec_GenKeyAndCSR': ','.join(params)})
rsp, status = wc.grab_json_response_with_status('/api/dataset', {'CSR_Format': '1'})
rsp, status = wc.grab_json_response_with_status('/api/function', {'Sec_DownloadCSRANDCert': '0,4,0'})
wc.request('GET', '/download/{0}'.format(rsp['FileName']))
rsp = wc.getresponse()
csr = rsp.read()
if rsp.getheader('Content-Encoding', None) == 'gzip':
csr = gzip.GzipFile(fileobj=io.BytesIO(csr)).read()
print(csr)
+88
View File
@@ -0,0 +1,88 @@
import subprocess
import os
class DiskInfo(object):
def __init__(self, devname):
self.name = devname
self.wwn = None
self.path = None
self.model = ''
self.size = 0
self.driver = None
self.mdcontainer = ''
devnode = '/dev/{0}'.format(devname)
qprop = subprocess.check_output(
['udevadm', 'info', '--query=property', devnode])
if not isinstance(qprop, str):
qprop = qprop.decode('utf8')
for prop in qprop.split('\n'):
if '=' not in prop:
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
self.path = v
elif k == 'ID_WWN':
self.wwn = v
elif k == 'MD_CONTAINER':
self.mdcontainer = v
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
if not isinstance(attrs, str):
attrs = attrs.decode('utf8')
for attr in attrs.split('\n'):
if '==' not in attr:
continue
k, v = attr.split('==', 1)
k = k.strip()
if k == 'ATTRS{size}':
self.size = v.replace('"', '')
elif (k == 'DRIVERS' and not self.driver
and v not in ('"sd"', '""')):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2'):
return 0
if 'imsm' in self.mdcontainer:
return 1
if self.driver == 'ahci':
return 2
if self.driver.startswith('megaraid'):
return 3
if self.driver.startswith('mpt'):
return 4
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def main():
disks = []
for disk in sorted(os.listdir('/sys/class/block')):
try:
disk = DiskInfo(disk)
disks.append(disk)
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
if __name__ == '__main__':
main()
+16
View File
@@ -0,0 +1,16 @@
import argparse
import pyghmi.redfish.command as cmd
import os
import sys
ap = argparse.ArgumentParser(description='Certificate Generate')
ap.add_argument('xcc', help='XCC address')
ap.add_argument('cert', help='Certificate in PEM format')
args = ap.parse_args()
c = cmd.Command(args.xcc, os.environ['XCCUSER'], os.environ['XCCPASS'],
verifycallback=lambda x: True)
wc = c.oem.wc
cert = open(args.cert, 'rb').read()
res = wc.grab_json_response_with_status('/api/function', {'Sec_ImportCert': '0,1,0,0,,{0}'.format(cert)
print(repr(res))
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/python
import os
import sys
import yaml
def mkdirp(path):
try:
os.makedirs(path)
except OSError as e:
if e.errno != 17:
raise
def makeboot_tree(distribution, profiledir):
bootdir = os.path.join(profiledir, 'boot')
efisrc = os.path.join(distribution, 'EFI')
efidir = os.path.join(bootdir, 'efi/boot')
mkdirp(efidir)
initrfsdir = os.path.join(bootdir, 'initramfs')
mkdirp(initrfsdir)
for directory in os.walk(efisrc):
for filename in directory[2]:
if filename.lower() == 'bootx64.efi':
srcfile = os.path.join(directory[0], filename)
trgfile = os.path.join(efidir, 'bootx64.efi')
os.link(srcfile, trgfile)
elif filename.lower() == 'grubx64.efi':
srcfile = os.path.join(directory[0], filename)
trgfile = os.path.join(efidir, 'grubx64.efi')
os.link(srcfile, trgfile)
netbootdir = os.path.join(distribution, 'images/pxeboot')
srckern = os.path.join(netbootdir, 'vmlinuz')
srcinitramfs = os.path.join(netbootdir, 'initrd.img')
trgkern = os.path.join(bootdir, 'kernel')
trginitramfs = os.path.join(initrfsdir, 'initrd.img')
os.link(srckern, trgkern)
os.link(srcinitramfs, trginitramfs)
trginitramfs = os.path.join(initrfsdir, 'site-initramfs.gz')
os.link('/var/lib/confluent/public/site/site-initramfs.gz', trginitramfs)
profileinfo = os.path.join(profiledir, 'profile.yaml')
with open(profileinfo) as info:
profile = yaml.load(info)
cfgfile = os.path.join(efidir, 'grub.cfg')
with open(cfgfile, 'w') as grubcfg:
grubcfg.write('set timeout=5\n')
grubcfg.write("menuentry '{0}' {{\n".format(
profile.get('label', 'Unknown')))
grubcfg.write(' linuxefi /kernel {0}\n'.format(profile.get(
'kernelargs', 'quiet')))
initrds = ' '.join(
['/initramfs/{0}'.format(x) for x in os.listdir(initrfsdir)])
grubcfg.write(' initrdefi {0}\n}}'.format(initrds))
#TODO: create the netboot grub.cfg, is there a way to use grub http
# without putting the server in the cfg?
# If server is omitted, value of environment variable ‘net_default_server’
# is used
if __name__ == '__main__':
makeboot_tree(sys.argv[1], sys.argv[2])