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

Compare commits

...

326 Commits

Author SHA1 Message Date
Jarrod Johnson 8eb242cba0 Add ipv6 support to genesis 2021-09-09 16:27:59 -04:00
Jarrod Johnson a4de3b93ec Merge branch 'master' into ip6 2021-09-08 12:44:05 -04:00
Jarrod Johnson a3486c5e68 Skip calling localectl when possible
localectl can sometimes be unworkable,
prefer sourcing the relevant configuration files directly
when possible.
2021-09-08 12:43:56 -04:00
Jarrod Johnson 465b973f79 Add unencrypted option to pack and rework multi-repo support
Support skipping encryption if a user does not want it. Further support mulitple '-r'
arguments and fix builds when
no -r is specified.
2021-09-08 12:43:50 -04:00
Jarrod Johnson 20f607f49e Skip calling localectl when possible
localectl can sometimes be unworkable,
prefer sourcing the relevant configuration files directly
when possible.
2021-09-08 12:42:43 -04:00
Jarrod Johnson 3c7034edf2 Add unencrypted option to pack and rework multi-repo support
Support skipping encryption if a user does not want it. Further support mulitple '-r'
arguments and fix builds when
no -r is specified.
2021-09-08 12:17:30 -04:00
Jarrod Johnson 3762a37243 Provide advertise and reply
Since the packets are the same, just need to handle the
request type.
2021-09-07 16:50:55 -04:00
Jarrod Johnson f3d7a949e6 Fix ipv6 netboot support to get to request
A number of mistakes were in
netutil and the advertise needed
to be complete and transmitted.
2021-09-07 16:13:27 -04:00
Jarrod Johnson 10e408559b Merge branch 'master' into ip6 2021-09-07 11:10:25 -04:00
Jarrod Johnson 6465203918 Add ability to pack and combine with a previous baseprofile
For example, this is a good complement to unpack in a tethered environment:

unpack profile-v1 /root/scratch
#modify /root/scratch
pack /root/scratch profile-v2 -b profile-v1
2021-09-03 16:14:44 -04:00
Jarrod Johnson de5350ca6e Fix additional repository argument for suse build 2021-09-03 15:12:06 -04:00
Jarrod Johnson 5422f904f9 Add a breadcrumb for scripts to detect exec context 2021-09-03 12:11:10 -04:00
Jarrod Johnson 57a8149844 Fix adding repositories to rhel-style diskless 2021-09-03 09:16:22 -04:00
Jarrod Johnson 558139d654 Fix bug around pulling in img build info 2021-09-02 12:11:48 -04:00
Jarrod Johnson 63d9aea218 Support relative paths for build scratchdir and pkglist 2021-09-02 12:10:07 -04:00
Jarrod Johnson 5d1d527e09 If reasonable, provide a 'distribution' link for diskless
If the user uses '-s', then
preserve that in distribution like a diskful one would.
2021-09-02 11:58:22 -04:00
Jarrod Johnson 19685b7a4e Accept relative path for unpack 2021-09-02 11:38:09 -04:00
Jarrod Johnson adcc2f0aa9 Add some usage to the volume flag for now '-' 2021-09-02 11:30:51 -04:00
Jarrod Johnson fbef6442d5 Add a shorthand '-' to say 'mount to same path as outside' 2021-09-02 11:29:40 -04:00
Jarrod Johnson bf017b55e1 Have unpack more intuitively target the specific directory
This makes it symmetric witht pack/unpack.
2021-09-02 11:22:45 -04:00
Jarrod Johnson bcc278e4d8 Add losetup for new suse support 2021-09-02 10:30:30 -04:00
Jarrod Johnson 0d2ecd460d Merge changes from EL8 imageboot 2021-09-02 10:17:11 -04:00
Jarrod Johnson ac45d6e578 Support non-interactive request to exec command
Make outside scripting easier by accepting an optional command
to run in exec environment.
2021-09-02 09:06:57 -04:00
Jarrod Johnson 237670e9b1 Rework gpg key import for suse build
Only pull in host keys when not using a source.
When using source(s), pull in all normal gpg keys from local filesystem sources.
2021-09-02 08:39:45 -04:00
Jarrod Johnson f65056a047 Fix Suse image fingeprinting 2021-09-02 08:05:11 -04:00
Jarrod Johnson 6264dc1ed1 Add host pubkeys to build root 2021-09-01 16:17:34 -04:00
Jarrod Johnson a7d894007f Treat netgroup entries differently 2021-09-01 11:00:53 -04:00
Jarrod Johnson 27a1649d94 Add more in-context stateless documentation
Specify what default behavior is and reason to choose one over
the other.
2021-09-01 10:44:25 -04:00
Jarrod Johnson 96f50519d8 Fix typo in imgutil 2021-09-01 10:38:49 -04:00
Jarrod Johnson 925cca1733 Add some commentary 2021-09-01 10:28:49 -04:00
Jarrod Johnson 9afa33d0cc Assisted redfish bmc setup
This is a quick example script of using
confluent switch scanning in conjunction with
checking dhcp leases to handle some generic redfish
bmc.
2021-09-01 10:23:43 -04:00
Jarrod Johnson 6c32630bd9 Fix multi-server reply processing
The stateless code could get confused by multiple offers,
fix the mistake.
2021-08-30 11:24:57 -04:00
Jarrod Johnson aaea5bcaeb Make sure the netlink socket closes on the way out 2021-08-30 09:01:42 -04:00
Jarrod Johnson 3d4462d788 Fix support for newer XCC firmware
Newer XCC firmware requires more parameters for usermodify
2021-08-27 18:29:38 -04:00
Jarrod Johnson d69de62077 Amend setinitalpwd 2021-08-27 16:37:26 -04:00
Jarrod Johnson c48f3a8b59 Sample script to navigate first password change 2021-08-27 16:13:14 -04:00
Jarrod Johnson ce98ff6b3d Fix suse imgutil build function 2021-08-27 09:03:35 -04:00
Jarrod Johnson e35ce77bc7 Fix SUSE imgutil 2021-08-27 08:46:47 -04:00
Jarrod Johnson c2de5fa580 Fix SUSE imgutil 2021-08-27 08:46:23 -04:00
Jarrod Johnson a9044238ba Further the IPv6 support 2021-08-26 17:44:30 -04:00
Jarrod Johnson dd1615b4a7 Fixes for imgutil 2021-08-26 16:03:38 -04:00
Jarrod Johnson f2fcbc887d Fixes for imgutil 2021-08-26 16:03:17 -04:00
Jarrod Johnson d4a846e786 Merge branch 'master' into ip6 2021-08-26 12:23:29 -04:00
Jarrod Johnson 7c617a160b Close socket in relay_data
Closing the socket outside of relay_data causes relay_data
to stay alive, causing eventlet to think a filehandle is open that is not.

A reasonable question would be why eventlet fails to error that read
when the filehandle is closed, but for now, move the close activity
to the relay_data handler.

This resolves "Second simultaneous read on fileno" conditions introduced
by the fix for leaking filehandles.
2021-08-26 11:52:22 -04:00
Jarrod Johnson d0c23e490b Tolerate delays in crypt-dm completion for imgutil 2021-08-26 10:38:34 -04:00
Jarrod Johnson 6bf51a20b2 Set nodename as hostname prior to post for el class distros 2021-08-26 09:33:16 -04:00
Jarrod Johnson 3e21587f22 Update error text for redfish to cover completely unsupported redfish
systems
2021-08-26 08:58:02 -04:00
Jarrod Johnson 1b3231377c Draft work to update pxe and netutil for ipv6 support 2021-08-25 17:58:36 -04:00
Jarrod Johnson ba7ccaa7f2 Document the netlink data in neighutil 2021-08-25 17:57:52 -04:00
Jarrod Johnson 093e961613 Merge branch 'master' into ip6 2021-08-25 15:59:06 -04:00
Jarrod Johnson b4ee1ab6af Fix imgutil build 2021-08-25 15:58:48 -04:00
Jarrod Johnson 3eb9ef720b Fix imgutil build 2021-08-25 15:58:17 -04:00
Jarrod Johnson 597db2138f Begin work on ipv6 netboot support 2021-08-25 09:21:12 -04:00
Jarrod Johnson 6bb6b362ab Provide support for merging multiple sources to single destination 2021-08-24 17:39:06 -04:00
Jarrod Johnson 0cfd18d84e Add a little main to experiment with neighutil 2021-08-24 17:18:10 -04:00
Jarrod Johnson 51a73737a7 Fix new neigh behavior
It would sometimes miss a lookup.
2021-08-24 17:15:56 -04:00
Jarrod Johnson 4f4d02d68f Refactor neighutil
Stick to bytes in the expensive part, only convert to and from
presentation form on lookup need.

Convert all external calls to a unified lookup logic.
2021-08-24 16:19:41 -04:00
Jarrod Johnson 797465b3eb Handle some intra-collective errors better 2021-08-24 15:50:03 -04:00
Jarrod Johnson 69b58836f6 Change to built in neigh fetch
Rather than outsource, slowly,
to ip neigh for this frequent task,
call netlink directly.
2021-08-24 11:54:33 -04:00
Jarrod Johnson 06cfd408fc Fix handling of abrupt client close 2021-08-24 07:57:14 -04:00
Jarrod Johnson b367d8ea50 Fix ntpcfg handling in autoyast 2021-08-20 11:12:22 -04:00
Jarrod Johnson 30e901ef19 Attempt -p and -r arguments to add repos and speciy alternate pakagke list. 2021-08-18 17:34:27 -04:00
Jarrod Johnson b44ac19723 Provide some info on image progeny on pack 2021-08-18 15:50:03 -04:00
Jarrod Johnson f855dda70f Sort list of distributions and profiles
The default sort order of listdir
isn't particularly helpful to anyone.
2021-08-18 15:40:58 -04:00
Jarrod Johnson b07ca72a8b Close stray filehandles
Proxied terminals and dispsatched
requests would leak filehandles.
2021-08-17 17:18:10 -04:00
Jarrod Johnson d3b6326673 Fix incorrect variable name for starting tmux 2021-08-12 16:47:03 -04:00
Jarrod Johnson 216a390229 Further refine TERM for tmux 2021-08-12 16:33:46 -04:00
Jarrod Johnson 93c52c6981 Improve tmux draw behavior in genesis 2021-08-12 16:31:26 -04:00
Jarrod Johnson ef43d91164 Have diskless environment prefer 'primary' interface too 2021-08-12 15:20:43 -04:00
Jarrod Johnson 130e3adbc6 Add uuid fill-in for pxe when policy allows
If permissive or open, and xcc is known, but uuid missing,
fill it in.

If open or pxe, and can know node through XCC fingerprint,
then accetp that as a clue.

Also, do not search ethernet switches when xcc cert helps identity.
2021-08-12 14:17:57 -04:00
Jarrod Johnson 0f543c80e9 Add a more specific error if we have a guess about nodename
If a manual add procedure has not
specified a mac or uuid, but
the discovery framework has a guess about it,
have that guess appear
in events with suggestion on
how to proceed.
2021-08-12 13:07:57 -04:00
Jarrod Johnson e9bc7ebe8d Finish conversion of imgutil to argparse 2021-08-12 09:15:15 -04:00
Jarrod Johnson 8a8e9718f9 Begin migration of imgutil to argparse
We no longer support python 2.6, we can use argparse for better subcommand handling.
2021-08-11 17:01:32 -04:00
Jarrod Johnson 865021beff Unack to rootfs
This keeps things neutral and leaves the door open for multipart
support
2021-08-11 16:14:46 -04:00
Jarrod Johnson 034eca3bb0 Refine PXE/HTTP log
Add logs for ignored boot requests, suppress discovery warning for known uuids,
and generally throttle logging these to once per mac address per minute.
2021-08-11 08:14:32 -04:00
Jarrod Johnson 6b28877828 Add iputils to have ping in suse 2021-08-11 07:31:58 -04:00
Jarrod Johnson 71342272bf Add imgutil unpack
Provide capability to unpack previously packed diskless images.
2021-08-10 09:04:30 -04:00
Jarrod Johnson 1e418e1bcf Stub to drive unpack feature 2021-08-09 16:47:27 -04:00
Jarrod Johnson 225618ef75 Add stall in firstboot for deployer to be available 2021-08-09 15:19:36 -04:00
Jarrod Johnson 2a0491ef0c Add ability to specify custom increment in []
Often, it's desired to target odd or even, provide custom increment/step syntax,
but only in the [] context.

Perhaps one day can add n1-n12:2 support, but for now, this is easier to handle.
2021-08-09 11:19:07 -04:00
Jarrod Johnson db9df97fa0 Check and error if user request non-existant profile
This provides nice and immediate feedback for a common mistake.
2021-08-09 09:49:10 -04:00
Jarrod Johnson f9846cb564 Fix inability to delete a completed servicedata event 2021-08-05 08:31:13 -04:00
Jarrod Johnson 91caf76cae Fix ownership problems with build and capture
Some parent directories were never given to confluent user
2021-08-04 12:12:57 -04:00
Jarrod Johnson 9bfdd20919 Add sanity check to confluent start
Refuse to start if /etc/confluent is in bad shape that
may create issues later.
2021-08-04 10:54:21 -04:00
Jarrod Johnson c3e342a8c0 Fix ordering of python path discovery 2021-08-03 16:48:30 -04:00
Jarrod Johnson 54fde4af21 Fix pessimistic error on apikey
Actually make sure that the apikey retrieval failed before erroring.
2021-08-03 16:44:03 -04:00
Jarrod Johnson fc22c87ce9 Fix typo in informational message 2021-08-02 16:03:37 -04:00
Jarrod Johnson 48d84a5422 Fix get_json
Some handlers may work in bytes or str,
normalize to str on the way out.
2021-08-02 14:35:59 -04:00
Jarrod Johnson b604ec4773 Fix detection of missing sync source
Rather than just assume we want a directory when glob
finds no matches, just take the empty result.
2021-08-02 11:33:08 -04:00
Jarrod Johnson 2219297afc Fix simple password support
Simple password was broken during the MFA addition, restore
the most common authentication mechanism.
2021-07-29 13:16:33 -04:00
Jarrod Johnson 08f226a3bf Recognize Ubuntu 20.04.2 2021-07-28 14:31:04 -04:00
Jarrod Johnson a25a10cb86 Add firstboot/post directories to ubuntu 2021-07-28 08:52:05 -04:00
Jarrod Johnson af9676ee6f Improve appearance of imgutil capture
Provide more feedback with less worrisome normal output.
2021-07-27 08:36:48 -04:00
Jarrod Johnson 4c96735b6d Add ssh to post phase of cloning 2021-07-26 13:51:36 -04:00
Jarrod Johnson ac86961e24 Perform syncfiles on clone deployment post 2021-07-26 13:29:33 -04:00
Jarrod Johnson 6adc736450 Relocate syncfiles path to correct location 2021-07-26 13:22:31 -04:00
Jarrod Johnson 8ab6fef632 Fix kernel selection on pack
Make a specialized sort
for kernel versions to more reliably identify the latest version.
2021-07-26 13:03:26 -04:00
Jarrod Johnson 065d0585ea Make search for grub.cfg more specific
grub.cfg outside of /boot can easily exist, be more specific about where it can be.
2021-07-26 12:17:15 -04:00
Jarrod Johnson ca8f6fa52c Bash completion for imgutil capture 2021-07-26 11:59:04 -04:00
Jarrod Johnson a720ccdbc8 Set ownership/permissions of private section
Fix confluent being unable to read, and make the permissions more consistent.
2021-07-26 11:12:59 -04:00
Jarrod Johnson 0ad59436ec Mitigate scratch consumption
As an old file is copied in for future disposale, delete
it as we go by fallocate
to punch holes in it.
2021-07-26 09:19:33 -04:00
Jarrod Johnson 663f8fc085 Add nodename to set_confluent_vars
It is expected that nodename would also be set.
2021-07-23 19:08:06 -04:00
Jarrod Johnson a7b9e72589 Fixes and update umage2disk to new multipart format 2021-07-23 19:06:14 -04:00
Jarrod Johnson 9eb6f38bf1 Various fixes to imageboot.sh 2021-07-23 18:43:57 -04:00
Jarrod Johnson 4928f50332 Create private directiories and correct key format 2021-07-23 17:43:47 -04:00
Jarrod Johnson 7f468c3a91 Correct offset to be in sectors, not bytes 2021-07-23 17:32:42 -04:00
Jarrod Johnson 81b4da6a95 Add encrypted stateless pack 2021-07-23 17:11:33 -04:00
Jarrod Johnson 4bde1b963f Transfer encryption key on capture 2021-07-23 16:54:57 -04:00
Jarrod Johnson 1e44deecf6 Add support to retrieve private key and crypt mount diskless/cloning 2021-07-23 16:49:02 -04:00
Jarrod Johnson 1e653851fe Add syncfiles to el8 diskless 2021-07-23 16:23:15 -04:00
Jarrod Johnson 54667570bd Create encrypted image and private profile data
Prepare for securing os profile witht custom images
2021-07-23 16:13:24 -04:00
Jarrod Johnson 364085801a Fix apikey variable 2021-07-21 17:44:43 -04:00
Jarrod Johnson 8171d461ea Another selinux fixup
systemctl enable firstboot
produces invalid selinux context, fix aafter enabling.
2021-07-21 17:34:53 -04:00
Jarrod Johnson 3690dda177 Actually enable firstboot execution 2021-07-21 16:48:38 -04:00
Jarrod Johnson 365b81e7e1 Prune all .gitignore files, not just blessed profiles 2021-07-21 13:57:06 -04:00
Jarrod Johnson b3fee922f9 Try to speed up selinux labelling
There's only a couple of places
where the imaging should need fixup, be more selective in relabel.
2021-07-21 13:26:31 -04:00
Jarrod Johnson 29d0dd6678 Add missing profile content for cloning 2021-07-21 12:47:43 -04:00
Jarrod Johnson 7d31e22447 Add post/firstboot to os cloning 2021-07-21 12:28:03 -04:00
Jarrod Johnson 4ddfa192ba Remove sticky bit from profile directory 2021-07-21 12:16:22 -04:00
Jarrod Johnson fa45ea8ad3 Finalize a captured image 2021-07-21 11:56:15 -04:00
Jarrod Johnson f4281e115b Fix permissions on generated profile. 2021-07-21 11:53:51 -04:00
Jarrod Johnson 5dfbeef79c Advance state of cloning
Have imgutil complete the capture process, splitting work
between target and repository.

Provide hook through kcmdline to induce installtodisk.

Have installimage reboot system cleanly when done.

Have new /etc/confluent in cloned system.

Hook for post scripts to execute.
2021-07-21 11:15:42 -04:00
Jarrod Johnson cd8a1dfe5e Draft work to flesh out profile and pull back
Fingerprint the target and prepare for more effort.

Will have another imgutil primitive for post-capture prep to get the
initramfs, kernel, rootimg, and bootloader content ready for copy.
2021-07-20 17:02:08 -04:00
Jarrod Johnson 2257a67420 Begin wiring imgutil for remote capture
Will be having to run on remote
system and local system
and combining the results
as well as cleaning up after ourselves on target.
2021-07-20 15:56:47 -04:00
Jarrod Johnson 55302b74d9 Have prototype cloning implemented
Go ahead and relabel all selinux content, ssh keys, grub, and efiboot entry.
2021-07-20 14:07:55 -04:00
Jarrod Johnson 22008f9dc9 Image cloning changes
Refactor and try to mask ssh
keys for root user.

Try to preserve selinux context for masked files.

Add progress indicator for writing to disk.
2021-07-19 17:30:26 -04:00
Jarrod Johnson db735a654d Aggregate vt buffer feeds
If we start developing a backlog
of content to feed to the buffer manager,
aggregate updates to batch submit them more efficiently.
2021-07-19 11:16:12 -04:00
Jarrod Johnson a8b54ff434 Fix initial collective join
Initial collective join combined
with the orderly collective startup
hit a chicken and egg problem.

Disable initting on first enrollment
to let enrollment drive
that specific initialization to
restore behavior.
2021-07-19 10:25:35 -04:00
Jarrod Johnson fada9336ee Fix swapsize float in image install
If it undergoes float arithmetic, it must be made int again.
2021-07-16 17:23:21 -04:00
Jarrod Johnson bda51d2106 Start fixup, first up is the fstab
Provide an fstab consistent
with redhat strategy, but with
the new uuids from imaging.
2021-07-16 17:02:53 -04:00
Jarrod Johnson 88d49c9f40 Start image2disk
This marks having written out
the partitions verbatim.
2021-07-16 16:29:56 -04:00
Jarrod Johnson c92b2f4255 Stage for python application to actually execute the install 2021-07-16 12:03:11 -04:00
Jarrod Johnson ece525c2e2 Begin work on install and filter small devices from install candidacy 2021-07-16 11:11:13 -04:00
Jarrod Johnson 386dc2348c Mount multiple partitions in diskless mode 2021-07-16 10:39:11 -04:00
Jarrod Johnson da44738e00 Generalize more of an OS on capture
/etc/fstab, hostname, and networnk-scripts are masked
for the image.
2021-07-15 17:30:50 -04:00
Jarrod Johnson e43e5ac167 Add confluent_imgutil to addons 2021-07-15 14:42:26 -04:00
Jarrod Johnson 9458d33cc5 Add multipart image support to diskless 2021-07-15 14:39:17 -04:00
Jarrod Johnson 831b129899 Correct syntax error in confluent client 2021-07-15 12:39:38 -04:00
Jarrod Johnson 1570d3dbe3 Add c utility for reading confluent multipart images 2021-07-15 12:39:19 -04:00
Jarrod Johnson 46c0852b6f Add more non-json data to image format 2021-07-15 12:38:50 -04:00
Jarrod Johnson e3bd1d6cac Correct format of confluent multipart magic 2021-07-15 11:24:49 -04:00
Jarrod Johnson 597faecc04 Change error for nodefirmware with present, but empty noderange 2021-07-15 11:24:23 -04:00
Jarrod Johnson 85643d82e8 Add losetup to el8 stateless
For multipart support, need to manually
set up loop mount and dmsetup.
2021-07-15 10:45:38 -04:00
Jarrod Johnson 7e07ec96a2 Combine json and images into a single multi-part file
This will facilitate urlmount use for mounting without
downloading.
2021-07-15 09:12:17 -04:00
Jarrod Johnson eb3dd0f10a Begin work on imgutil capture
Provide mechanism to capture for cloning purposes.
2021-07-14 17:36:55 -04:00
Jarrod Johnson a953a6afba Provide clearer error when osdeploy initialize is not done
osdeploy import needs to have things prepared by
osdeploy initialize.  Check for it having run and error if needed.
2021-07-14 15:40:38 -04:00
Jarrod Johnson f7a80c6ac0 Fix nodeattrib when a list member is 'broken'
If 'broken' was used as a group name, it would behave incorrectly.
Only check 'broken' for dict types.
2021-07-14 08:06:52 -04:00
Jarrod Johnson 101b5685d1 Move kickstart.custom fetch to before pre.d
Allow custom scripts to further customize kickstart.custom
by modifying /tmp/kickstart.custom
2021-07-12 09:23:01 -04:00
Jarrod Johnson 71d8b89a57 Fix profile name not being set in time 2021-07-09 16:22:57 -04:00
Jarrod Johnson ea26478714 Add needed dependency for OFED 2021-07-08 11:54:14 -04:00
Jarrod Johnson 175f9317ac Skip 32bit portion of mofed 2021-07-08 11:45:43 -04:00
Jarrod Johnson b291dee68c Add bind-utils, skip iwl*-firmware, and alphabatize packages 2021-07-08 11:38:50 -04:00
Jarrod Johnson 4f77615161 Provide a sample container build 2021-07-08 09:39:36 -04:00
Jarrod Johnson 430428eba2 Add missing dependencies to the confluent server package 2021-07-08 09:33:35 -04:00
Jarrod Johnson 4dee174bee Merge pull request #64 from vmaneagit/patch-64
Update nodeconfig.ronn
2021-07-08 09:25:11 -04:00
Jarrod Johnson 766a5e0fd2 Merge pull request #61 from vmaneagit/patch-62
Update confluentdbutil.ronn
2021-07-08 09:23:54 -04:00
Jarrod Johnson 90a3839353 Merge pull request #65 from vmaneagit/patch-65
Update nodeeventlog
2021-07-08 09:23:32 -04:00
Jarrod Johnson ce17015baf Merge pull request #68 from vmaneagit/patch-68
Update nodelicense.ronn
2021-07-08 09:22:41 -04:00
Jarrod Johnson 360ff0798c Merge pull request #69 from vmaneagit/patch-69
Update noderemove
2021-07-08 09:22:24 -04:00
Jarrod Johnson c2bd12234b Merge pull request #70 from vmaneagit/patch-70
Update nodersync.ronn
2021-07-08 09:22:03 -04:00
Jarrod Johnson 51f1c67a64 Merge pull request #63 from vmaneagit/patch-63
Update nodeconfig
2021-07-08 09:21:50 -04:00
Jarrod Johnson c3634fe75c Merge pull request #66 from vmaneagit/patch-66
Update nodelicense
2021-07-08 09:21:34 -04:00
Jarrod Johnson 8f394f230c Merge pull request #71 from vmaneagit/patch-71
Update nodersync
2021-07-08 09:21:20 -04:00
Jarrod Johnson b21d462acf Merge pull request #72 from vmaneagit/patch-73
Update noderun.ronn
2021-07-08 09:20:51 -04:00
Jarrod Johnson dc32813882 Merge pull request #73 from vmaneagit/patch-72
Update noderun
2021-07-08 09:20:38 -04:00
Jarrod Johnson c8ead7bb6d Merge pull request #74 from vmaneagit/patch-74
Update nodepower.ronn
2021-07-08 09:19:56 -04:00
Jarrod Johnson f352a10000 Merge pull request #75 from vmaneagit/patch-78
Update nodepower
2021-07-08 09:19:39 -04:00
Jarrod Johnson dd4f329139 Finish changes to builddeb script for vtbufferd 2021-07-01 16:45:20 -04:00
Jarrod Johnson 90bde09c82 Fix make clean without binary existing 2021-07-01 16:39:36 -04:00
Jarrod Johnson 2d2e8114b3 Add install section for Makefile in debian packaging 2021-07-01 16:34:06 -04:00
Jarrod Johnson b87580466e Add debian packaging for vtbuffered 2021-07-01 16:31:59 -04:00
Jarrod Johnson 08cf1bbf48 Fix the sed syntax for LEAP import 2021-07-01 14:07:43 -04:00
Jarrod Johnson e565a1752f Fix LEAP initprofile behavior 2021-07-01 13:43:31 -04:00
Jarrod Johnson ef1649208e Switch to using separate CA for TLS
This allows regenerating TLS cert
without updating boot images.

For example, if ip address changes need a new cert, no
longer should the nodes need new certs to trust
just due to that.
2021-06-30 14:25:46 -04:00
Jarrod Johnson 35b9635840 Clear armed API if current node token is used
If a node is armed, but instead unseals the prior key from TPM,
implicitly clear the armed state to avoid leaving it armed.
2021-06-28 13:30:09 -04:00
Jarrod Johnson 9c43dbff47 Rework MFA handling
Avoid calling PAM in the parent process, as
this seems to cause problems with some PAM
configurations.
2021-06-28 11:34:11 -04:00
Jarrod Johnson f830514d10 Implement support for additional pam prompts
For example, if PAM has OTP, then support it.
2021-06-25 17:26:32 -04:00
Jarrod Johnson f2eba22b9b Fix TLS certs for el8 diskless
Properly place and process
the TLS certs for a site.
2021-06-25 13:06:35 -04:00
Jarrod Johnson 1fcab688dd Fix connection name in networkmanager diskless 2021-06-25 10:56:35 -04:00
Jarrod Johnson abfa2c4f7c Switch back to default curl output
The terminal size on console is a challenge.
2021-06-24 17:01:35 -04:00
Jarrod Johnson 3be73af07e Change style of download progress in curl
Use a simpler progress bar.
2021-06-24 16:46:10 -04:00
Jarrod Johnson a2b2c8a995 Remove extraneous '/' output
Suppress output of cd -, as
it's a bit odd during boot.
2021-06-24 15:57:03 -04:00
Jarrod Johnson 42f8056d56 Fix apiclient with TPM managed token
The retry mechanism is amended
to clear out the useless key
and start trying to get a network grant again.
2021-06-24 14:53:54 -04:00
Jarrod Johnson 2ef695324a Migrate genesis to new TPM strategy
Have addons for genesis
implement the same TPM usage
model as the suse/redhat stateless.
2021-06-24 14:35:21 -04:00
Jarrod Johnson 4c6f0843f9 Remove microcode from genesis
Should not be needed for genesis level activity, and consumes a large
amount of storage.
2021-06-24 14:00:19 -04:00
Jarrod Johnson c19ae8a451 Add tpm2 tools to genesis
Follow the design of the stateless usage of TPM
2021-06-24 13:20:47 -04:00
Jarrod Johnson a8e152cc4a Switch TPM strategy on RedHat diskless
Switch to thte same approach as used in suse:
-Try to unseal any persistent handles
-If that works, try to use it on network
-If it didn't work, clear that handle
-When an api key is retrieved, then seal it to pcr 15
-When it's all done, extend pcr15 to prevent the OS from being able to
unseal
2021-06-24 12:04:10 -04:00
Jarrod Johnson c92b3aea9d Mitigate error output from extraneous handles
Unrelated handles in use will no longer result in misleading console
output.
2021-06-24 11:41:34 -04:00
Jarrod Johnson 5be4a5ab73 Add missing TPM utilities to suse boot 2021-06-24 11:22:41 -04:00
Jarrod Johnson 3c41c52d77 Rework TPM usage in SUSE diskless
For one, need to detect stale
TPM value and clear them.

For another, seal to PCR 15 and extend after unlock, so that the booted
system is unable to retrieve
the data from the TPM (e.g.
a plain user by default is allowed
to unseal data if there's no
policy, so use a policy and
extend the state away before boot)
2021-06-24 11:09:37 -04:00
Jarrod Johnson e24a3a7231 Change media_url
Have autoyast file pass validation and adapt
the processing to work with it.
2021-06-24 08:27:55 -04:00
Jarrod Johnson bffb7a8cac Correct typo in suse install autoconsole message 2021-06-23 17:52:21 -04:00
Jarrod Johnson feb418ac59 Store TPM unsealed apikey in usual location 2021-06-23 17:22:18 -04:00
Jarrod Johnson ee5ea4263f Add curl to suse15 pkglist 2021-06-23 17:16:13 -04:00
Jarrod Johnson b30fabd55d Enable TPM2 on SUSE diskless for apikey
Rather than remote sealed copy, store it in the TPM2

Will convert genesis and EL diskless for this to be the new preferred
mechanism.
2021-06-23 17:01:27 -04:00
Jarrod Johnson b8c9e9c535 Begin work to support complex PAM conversations
For example, TOTP setups need
more prompts, this will pass
the info to the client for the client to adjust.
2021-06-23 16:31:42 -04:00
Jarrod Johnson d86fc664e9 Handle space delimiting in nameservers
If multiple dns servers, then need to quote to preserve
the list.
2021-06-23 12:35:54 -04:00
Jarrod Johnson 6862d9e580 Correct formatting of nameserver list in suse 2021-06-23 12:26:49 -04:00
Jarrod Johnson dc8cb1b13f Correct syntax in imageboot for suse 2021-06-23 12:24:36 -04:00
Jarrod Johnson f10d2af59f Specify netconfig file location 2021-06-23 12:16:08 -04:00
Jarrod Johnson 172bb12885 Modify Suse diskless for suse networking
Suse doesn't use network manager, populate sysconfig
instead.
2021-06-23 12:07:13 -04:00
Jarrod Johnson 4445b8cc78 Fix name resolution for suse hosts/containers
Suse uses a strategy with symlinks, adapt
the resolv.conf target based on findings from
symlink chasing.
2021-06-23 11:49:16 -04:00
Jarrod Johnson b2fa2d92c5 Correct formatting mistake in os profile label in diskless 2021-06-23 11:32:43 -04:00
Jarrod Johnson fc19ca4e36 Change to pythton-dnspython for dependency
Multiple compatible packages exist that provide same name, accept
either.
2021-06-23 08:37:00 -04:00
Jarrod Johnson 23231e2b75 Have Suse15 diskless prep initrd and enable sshd 2021-06-22 16:59:12 -04:00
Jarrod Johnson 9ad5f52eed Package up suse diskless support 2021-06-22 16:37:04 -04:00
Jarrod Johnson 76f3537a79 Further advance SUSE15 diskless support 2021-06-22 16:18:32 -04:00
Jarrod Johnson b26b46dc41 Crate dracut module for suse15 2021-06-22 14:49:15 -04:00
Jarrod Johnson deec9b111a Initial phase of suse diskless support 2021-06-22 14:29:28 -04:00
Jarrod Johnson 59e6dc80b3 Remove commented, non-working concept code
The code was going to replace XInclude with something more manual
from sed and xml comments, but yast strips the comments.

So we instead manually make hooks for the replacement items.
2021-06-22 12:21:18 -04:00
Jarrod Johnson e34d76f7eb OpenSUSE 15.3 support
A number of changes in opensuse 15.3 require modifying our
strategy.

No more XInclude. This seems to be unintentional, but it released
and so we will work around it.

Some somewhat incorrect values, as pointed out by new validation.
2021-06-22 12:19:54 -04:00
vmaneagit 7f74476e5a Update nodepower
Updated line 35 to address the syntax mismatch between usage and man page SYNOPSIS
2021-06-16 18:25:10 +03:00
vmaneagit d201358c78 Update nodepower.ronn
Updated lines 6 and 30 to address the syntax mismatch between usage and man page SYNOPSIS
2021-06-16 18:19:49 +03:00
vmaneagit 902f9831cd Update noderun.ronn
Updated line 6 to address the syntax mismatches between usage output and man page SYNOPSIS
Line 18 has been updated to address the options listed differently between usage output and man page
2021-06-16 17:36:56 +03:00
vmaneagit 3723eadcfc Update noderun
Syntax mismatches between usage output and man page SYNOPSIS

Updated line 42:

        usage="Usage: %prog [options] <noderange> <command expression>",
2021-06-16 17:33:26 +03:00
vmaneagit 52d7bcbdb9 Update nodersync
Updated line 43:

        usage="Usage: %prog <file/directorylist> <noderange>:<destination>",

Syntax mismatch between usage output and man page SYNOPSIS
2021-06-16 17:23:18 +03:00
vmaneagit f33e35cebf Update nodersync.ronn
Updated line 15:

* `-f COUNT`, `-c COUNT`, `--count=COUNT`:

Options on SYNOPSIS page different than usage options
2021-06-16 17:23:03 +03:00
vmaneagit 2f00b31eb0 Update noderemove
Updated line 36:

    usage='''\n       %prog <noderange>

SYNOPSIS has < > around noderange whereas usage output does not
2021-06-16 17:11:11 +03:00
vmaneagit bbad704f43 Update nodelicense.ronn
Updated the syntax mismatch between usage output and man page SYNOPSIS

Line 6 was modified:

`nodelicense <noderange> [list][install <filename>|save <directory>|delete <license>]`
2021-06-16 16:58:21 +03:00
vmaneagit a05a95433d Update nodelicense
Fixing syntax mismatch between usage output and man page SYNOPSIS

Updated line 39

          "%prog <noderange> [list][install <filename>|save <directory>|delete <name>]")
2021-06-16 16:49:53 +03:00
vmaneagit 15d4247cf1 Update nodeeventlog
usage output doesn't have < > around noderange were as SYNOPSIS on man page does

Usage: nodeeventlog [options] noderange [clear]
SYNOPSIS: nodeeventlog [options] <noderange> [clear]

Line 40 has been updated:

    usage="Usage: %prog [options] <noderange> [clear]")
2021-06-16 16:17:08 +03:00
vmaneagit 4b45c148c8 Update nodeconfig.ronn
Updated the Syntax mismatch between usage output and man page SYNOPSIS

Options -b settings.batch, --batch=settings.batch and 
-r COMPONENT, --restoredefault=COMPONENT

were only shown as -b, --batch and -r, --restoredefault
2021-06-16 15:30:01 +03:00
vmaneagit 47a8da5ed0 Update nodeconfig
updated line 46:
argparser = optparse.OptionParser(usage="Usage: %prog [options] <noderange> [setting|setting=value]")
to match the man SYNOPSIS

nodeconfig [options] <noderange> [setting|setting=value]
2021-06-16 15:26:28 +03:00
Jarrod Johnson f0693f6ee5 Correct typo in imgutil 2021-06-15 13:45:27 -04:00
Jarrod Johnson 84634afc9c Improve imgutil brevity
Allow it to take only the basename and
default to likely /var/lib/confluent locations
Draft work on tab completion for imgutil.

Technically the tab completion should be in the imgutil package,
but for now bundle with server.
2021-06-15 13:30:30 -04:00
Jarrod Johnson 5621d48ffa Fix syntax error with new check 2021-06-15 12:23:45 -04:00
Jarrod Johnson 55a4211e71 Fix imgutil volume mounting 2021-06-15 12:12:27 -04:00
vmaneagit 03b644be7f Update confluentdbutil.ronn
Man page is missing part of the password option as shown under usage

from usage output:
-p PASSWORD, --password=PASSWORD
2021-06-15 17:28:30 +03:00
Jarrod Johnson 802ba9c708 Merge pull request #60 from vmaneagit/patch-61
Update collective.ronn
2021-06-15 09:42:48 -04:00
Jarrod Johnson 490827fe3a Allow memory reclamation through deletion
When going to zram, things were solid for space reduction as
data was written, however memory could no longer be reclaimed.

It turns out that zram supports TRIM, and by telling xfs discard,
we have it do trim-on-demand. It is by default off out of performance
concerns, but I don't think that applies to a zram backed filesystem.
2021-06-15 09:36:44 -04:00
vmaneagit 85568091fa Update collective.ronn
Added correction  for:

"collective gencert collective delete" should be on 2 separate lines.

correction should be:

collective gencert
collective delete
2021-06-15 16:25:26 +03:00
Jarrod Johnson 2ecab0432c Fix imageboot.sh issues for diskless boot 2021-06-15 08:58:21 -04:00
Jarrod Johnson dee03e1359 Attempt to updateboot at end of pack 2021-06-15 08:41:33 -04:00
Jarrod Johnson 3f87696978 Fix typo in imageboot.sh script 2021-06-15 08:38:27 -04:00
Jarrod Johnson 38a4e20b9a Fix issues around imageboot and source in functions 2021-06-15 08:31:45 -04:00
Jarrod Johnson 1d8d0699bc Fix ability to copy profiles during pack 2021-06-15 08:11:31 -04:00
Jarrod Johnson 62ac393721 Fix imgutil profile path and osdeploy diskless layout 2021-06-15 08:00:09 -04:00
Jarrod Johnson 1d148afd70 Add sourcing of functions to dracut hook 2021-06-14 18:46:36 -04:00
Jarrod Johnson e6550d8160 Fix use of label in profile.yaml 2021-06-14 16:55:32 -04:00
Jarrod Johnson 0079e5c437 Change imgutil packaging and pkglist path 2021-06-14 16:46:23 -04:00
Jarrod Johnson cce2c6d6db Add missing Release and remove stale binaries from git 2021-06-14 16:16:19 -04:00
Jarrod Johnson e32e40f451 Correct build script typo 2021-06-14 16:14:13 -04:00
Jarrod Johnson e50423881d Correct spec name in imgutil build script 2021-06-14 16:12:43 -04:00
Jarrod Johnson 82b94e3ff9 Add build script for imgutil 2021-06-14 16:12:01 -04:00
Jarrod Johnson 03cb065342 Fix inconsistency with naming of profiles 2021-06-14 16:09:48 -04:00
Jarrod Johnson cb4b8ab06f First pass at packaging imgutil 2021-06-14 16:02:52 -04:00
Jarrod Johnson ac2474f43e Attempt to fixup ownership, if applicable on the output 2021-06-14 15:17:54 -04:00
Jarrod Johnson e678ca24f9 Attempt to pull in osdeploy profile information 2021-06-14 14:59:59 -04:00
Jarrod Johnson 60aef0767c Pull in the diskless profile content for os deploy 2021-06-14 14:38:38 -04:00
Jarrod Johnson 15f5ec9362 Rename common to 'default' 2021-06-14 14:37:40 -04:00
Jarrod Johnson 870c8255f4 Move diskless to el8-diskless
The code so far is EL8 specific, properly
organize it.
2021-06-14 14:36:23 -04:00
Jarrod Johnson 795645f90a Extract boot logic from 'addons.cpio'
Make it easier to see and customize image boot script
behavior, leaving addons.cpio only to bring up network
and set up ssh.
2021-06-14 14:33:35 -04:00
Jarrod Johnson 99a42af907 Add zram module to stateless images 2021-06-14 14:31:57 -04:00
Jarrod Johnson 24d33256ca Fix typo in dracut configuration 2021-06-14 13:45:44 -04:00
Jarrod Johnson 334ec3a74f Refactor for multi-os support and improve initramfs experience
Organize el8 specific content to separate spaces for future
development of other OS support

Also, make the diskless initramfs a natural result of
dracut activity, so that normal rpm updates and oob driver
installs produce the expected initramfs without further
intervention.
2021-06-14 13:09:25 -04:00
Jarrod Johnson 261def8de2 Rename and refactor imgutil to separate rpm 2021-06-14 07:39:01 -04:00
Jarrod Johnson 25f65a278f Notation on untethered option 2021-06-11 16:18:59 -04:00
Jarrod Johnson f565030087 Switch to zram for overlay
Further mitigate unevictable memory penalty
for written files in stateless.
2021-06-11 16:16:17 -04:00
Jarrod Johnson 8edd2a28e4 Merge branch '3.2' 2021-06-10 18:51:56 -04:00
Jarrod Johnson 7122c17ce0 Remove pyte requirement
We no longer use pyte, remove the requirement.
2021-06-08 16:43:06 -04:00
Jarrod Johnson f5f16dac7a Fix nodebmcreset partial success
The error handling is improved, to
catch both noderange global issues as well
as partial completions.
2021-06-08 09:12:33 -04:00
Jarrod Johnson fa3220f3d1 Actually fix up the osdeploy build
For now, stub out the diskless profile dir, which doesn't quite
exist yet.
2021-06-04 17:45:52 -04:00
Jarrod Johnson 14bc4df92e Fix path mistake in osdeploy build script 2021-06-04 17:33:14 -04:00
Jarrod Johnson 1645d47b73 Fix clortho
The suggested correection for clortho was in fact
incorrect.  Revert back and cast it.
2021-06-04 17:19:19 -04:00
Jarrod Johnson bbe9bc3e06 Constrain plugin collections to flat by default
When asking for a path that exceeds the plugincollection,
thten provide generic 'not found' behavior.
2021-06-04 14:48:31 -04:00
Jarrod Johnson 58157b23d7 Error if noderange includes too many )
By default, pyparsing consumes only as much of the
input as matches the grammar. Tell it to consume all
of the noderange and error if there's more string than
matches our grammar.
2021-06-04 14:19:06 -04:00
Jarrod Johnson b8ddf149bd Skip newer agent behaviors with incompatible old ssh
The previous attempt to support older ssh failed to completely
enact old behavior when needed.
2021-06-04 13:40:39 -04:00
Jarrod Johnson bf158bc5aa Prep for diskless specific profile categories 2021-06-03 18:20:47 -04:00
Jarrod Johnson 8228c345bc Fix the build path of the stateless path 2021-06-03 18:12:18 -04:00
Jarrod Johnson 8eecd03d4b Correct path name in build process 2021-06-03 17:50:01 -04:00
Jarrod Johnson 044ae53ae3 Add symlink for site, and placeholder for addons
Will need to detect the proper 'category' for
the addons link.
2021-06-03 17:20:41 -04:00
Jarrod Johnson 1a30876a2d Begin work to package diskless support
First will work on the 'addons' portion of the needed
work.
2021-06-03 17:17:42 -04:00
Jarrod Johnson e0c59cc341 Fixup c utilities and add start_root
The diskless will use start_root to boot the 'main' OS as a container.
2021-06-03 17:06:10 -04:00
Jarrod Johnson 93c21d4148 Commit to python3 ofr imgutil
The imgutil will never be supported with python2,
so we will go ahead and commit to python.
2021-06-03 16:52:22 -04:00
Jarrod Johnson e23a88a4c3 Have resolv.conf more reliably work
Also, make sure it is read-only in the exec case
2021-06-03 15:47:17 -04:00
Jarrod Johnson 1289f95584 Merge branch '3.2' 2021-06-03 14:22:38 -04:00
Jarrod Johnson db965f133e Add a 'pack' and attempt to bind /etc/resolv.conf on exec
This facilitates custom manual image work
2021-06-02 17:26:46 -04:00
Jarrod Johnson 2073926256 Improve ctrl-c and other behaviors of osdeploy import
More reliably delete an import attempt to avoid odd behaviors.
2021-05-27 16:10:06 -04:00
Jarrod Johnson 734e12f0f1 Amend long wait to only apply to shutdown
The previous 30 seconds was a good amount to
wait for everything but a graceful shutdown.

The new 5 minute wait is too long to wait to
find out a system can't turn on.

Apply the 5 minute wait only on clean shutdown
request.
2021-05-27 09:57:07 -04:00
Jarrod Johnson deb2b98e7f Merge pull request #59 from zhougj4/master
[merge-LXCA] pull request
2021-05-27 09:54:44 -04:00
Zhou Guangjun 32c84993b0 [merge-LXCA] fix problem in merge with upstream - htmlify the dict
Details:
Revision: d9cc888e68abe7794ade82a251c4dffe9108a014
Author: Michael Du <duxd2@LENOVO.COM>
Date: 2017/8/2 21:06:22
2021-05-27 09:58:05 +08:00
Jarrod Johnson 48ff1fd12c Fix background mode of urlmount
Need the recheck thread managed in the background process if using
urlmount the normal way.
2021-05-26 18:21:06 -04:00
Jarrod Johnson bca5d9497f Implement failback facility
Treat the url list as an ordered list of priority. Occasionally check
the more preferred paths for health if they had been detected as bad.
2021-05-26 17:54:33 -04:00
Jarrod Johnson 9be039b8e5 Continue advancing the img build and exec utility 2021-05-26 15:18:01 -04:00
Jarrod Johnson a3266ad01c Begin next draft of genimage
Write in python and support sourcing
only from local directories if desired.
2021-05-25 17:27:07 -04:00
Jarrod Johnson e1a4f370b2 Implement node name subtitution in nodeshell
If wanting to ssh to 'altenative interfaces', provide -s to facilitate that
behavior, with added flexibility
compared to previous incarnations.
2021-05-25 15:46:22 -04:00
Jarrod Johnson a7e7e647f3 Fix hang on ctrl-e,c,p,o
Leave the function to
consume termdata if data
is available.
2021-05-25 14:26:49 -04:00
Jarrod Johnson 9bdef5bbb9 Fix typo in confetty 2021-05-25 11:58:20 -04:00
Jarrod Johnson af10e0ea91 Explicitly provide content-length 0 on 204
For some vintages of eventlet+apache, this is required
to avoid invalid responses from the server.
2021-05-25 10:18:13 -04:00
Jarrod Johnson 8455d30687 Disable selinux for now in a built image 2021-05-24 18:30:48 -04:00
Jarrod Johnson 903160f748 Fix the draft genimage script 2021-05-24 17:37:47 -04:00
Jarrod Johnson 52005dd6ae Further flesh out diskless profile 2021-05-24 16:20:54 -04:00
zhougj4 7363af866f Merge branch 'lenovo:master' into master 2021-05-24 10:46:50 +02:00
Jarrod Johnson 9ca42081de Try to add onboot service to diskless
It didn't quite work yet, but commit the progress so far.
2021-05-21 17:07:32 -04:00
Jarrod Johnson 6c6b766f37 Fix non-root node to node ssh in diskless 2021-05-21 13:18:29 -04:00
Jarrod Johnson a9b412fb81 Add node to node ssh to diskless 2021-05-21 12:41:58 -04:00
Jarrod Johnson 7c4b500e92 Add boot.img function to CoreOS 2021-05-21 11:11:01 -04:00
Jarrod Johnson 31ca787380 Register the service as needed for startup in CoreOS init 2021-05-21 07:50:50 -04:00
Jarrod Johnson 0ee67b8584 Mak the script executable for coreos bringup 2021-05-21 07:48:25 -04:00
Jarrod Johnson 19874d8032 Add coreos support to build 2021-05-21 07:32:47 -04:00
Jarrod Johnson 086def0bb8 Updates to the coreos support
Bring in and let ignition do the driving if the import had one.
2021-05-20 17:33:22 -04:00
Jarrod Johnson 9ac4cf0641 Rename generically to coreos
We should be able to support either RH or Fedora
flavors.
2021-05-19 17:28:05 -04:00
Jarrod Johnson 52b39e632d Draft CoreOS support
Preliminary work toward supporting CoreOS
2021-05-19 17:12:56 -04:00
Jarrod Johnson e167949db2 Fix comments and improve randomness
Since the granularity on time could cause a boot to
select the same 'random' entry, use better seed
for rand()
2021-05-19 11:05:58 -04:00
Jarrod Johnson 4ed9219704 Fix overruning the urls buffer
Technically, MAX_URL_PATHS would be one past the
buffer allocated, so it needs to stay under
that value.
2021-05-19 09:22:13 -04:00
Jarrod Johnson 5e1c366e0b Publish urlmount.c as 'misc'
It hasn't been packaged yet, but make it public while getting
ready to package.
2021-05-19 09:03:06 -04:00
Jarrod Johnson 85c3ca8c75 Pass root password onto diskless if set
This honors the administrator
setting the password in the
confluent database for the diskless node.
2021-05-18 15:25:26 -04:00
Jarrod Johnson a1ddb2323a Have networkmanager be content with diskless
Create an .nmconnection file
that sastisfies networkmanager
about the interface that confluent brought up.
2021-05-18 15:24:43 -04:00
Jarrod Johnson efd7f1de63 Conditionally apply agent to sshutil
Older OSes (RHEL7/SLES12) cannot
do ssh-keygen with an agent.

Degrade to classic confluent behavior when that happens.
2021-05-18 12:28:22 -04:00
Jarrod Johnson b63565972b Improve error on nodeconfig parse errors
The current error is vague and confusing, clarify with
more data showing what went wrong.
2021-05-18 10:33:27 -04:00
Jarrod Johnson bb1559c8a2 Add support for Intel E810 (ice) to confluent genesis 2021-05-18 09:56:02 -04:00
Jarrod Johnson a93a8d5cd1 Add ice driver to default diskless images 2021-05-18 09:55:27 -04:00
Jarrod Johnson af06c150b0 Add error message for long profile names
If a profile name pushes the filename field of dhcp
beyond what it can support, log an event and
do not offer a corrupted
dhcp offer packet.
2021-05-18 09:44:02 -04:00
Jarrod Johnson 7e14b41ef4 Fix typo in the genimage script 2021-05-14 16:40:48 -04:00
Jarrod Johnson 9c8529e9b4 Fix network interface names in confluent diskless 2021-05-14 16:36:43 -04:00
Jarrod Johnson 90daf5d5e3 Further advance the confluent diskless support 2021-05-14 15:58:57 -04:00
zhougj4 a28cd1055f Merge branch 'lenovo:master' into master 2021-05-14 08:07:23 +02:00
Jarrod Johnson 103b398629 Begin work to use urlmount 2021-05-13 18:58:37 -04:00
Jarrod Johnson cc9bb0533d Change CSV to have unix style line endings
The excel dialog has nice minimal quoting, but DOS line endings.

Unix dialog has good line endings, but excessive quoting.

Create a hybrid dialect and use it for nodesensors output.
2021-05-13 10:38:02 -04:00
Zhou Guangjun 32db72e38f [merge-LXCA] (138083) [ipmi] Restart Normally and Power off Normally job failed with the message Ensure that the endpoint is reachable on the network from LXCA
Details:
Revision: d02e5b653f764aeca44efd108acaabc8b173b482
Author: Zhou Guangjun <zhougj4@lenovo.com>
Date: 2018/10/17 17:26:39
2021-05-13 14:34:02 +08:00
Jarrod Johnson 51dece94ab Add draft for what will be genimage
Still very much prototype, but put it in github
to prepare to bring it together.
2021-05-12 18:13:55 -04:00
Jarrod Johnson 689418c8f1 Merge remote-tracking branch 'lenovo/master' 2021-05-12 17:32:51 -04:00
Jarrod Johnson df2604291d Fix refactored inline command run
This addresses the mistake in
refactoring the inline command
shortcut
2021-05-12 13:48:44 -04:00
Jarrod Johnson 9b7a40a358 Merge pull request #58 from zhougj4/master
[merge-LXCA] Add support for graphical consoles
2021-05-12 07:31:50 -04:00
Zhou Guangjun 9648f1c8ac [merge-LXCA] Add support for graphical consoles
Details:
Revision: a0f0309ee0bc731463794da9d685fa56bc62bc6f
Author: Allan Vidal <avidal@lenovo.com>
Date: 2015/11/26 3:50:31
2021-05-12 10:49:13 +08:00
Jarrod Johnson 9f45b0ed56 Draft support for stateless images
Aim is to support a few modes of operation:
-tar.xz, extracted to memory (most inefficient, least dependency)
-nfs read-only (less secure, more efficient, depends on getting nfs
export just right)
-nfs image read-only (same as above, but using squashfs to make overlay
more easily happily)
-gocryptfs on nfs (this provides encryption and integrity check)
2021-05-11 17:05:12 -04:00
136 changed files with 5430 additions and 578 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ def bailout(msg, code=1):
sys.exit(code)
argparser = optparse.OptionParser(usage="Usage: %prog [options] noderange [option|option=value]")
argparser = optparse.OptionParser(usage="Usage: %prog [options] <noderange> [setting|setting=value]")
argparser.add_option('-c', '--comparedefault', dest='comparedefault',
action='store_true', default=False,
help='Compare given settings to default or list settings '
+17
View File
@@ -89,6 +89,23 @@ def main(args):
cleararm(args.noderange, c)
clearpending(args.noderange, c)
elif args.profile:
profnames = []
for prof in c.read('/deployment/profiles/'):
profname = prof.get('item', {}).get('href', None)
if profname:
profname = profname.replace('/', '')
profnames.append(profname)
if profname == args.profile:
break
else:
sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
if profnames:
sys.stderr.write('The following profiles are available:\n')
for profname in profnames:
sys.stderr.write(' ' + profname + '\n')
else:
sys.stderr.write('No deployment profiles available, try osdeploy fiimport or imgutil capture\n')
sys.exit(1)
armonce(args.noderange, c)
setpending(args.noderange, args.profile, c)
else:
+1 -1
View File
@@ -37,7 +37,7 @@ if sys.version_info[0] < 3:
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange [clear]")
usage="Usage: %prog [options] <noderange> [clear]")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to clear if clearing log, '
+5 -1
View File
@@ -147,10 +147,12 @@ def update_firmware(session, filename):
def show_firmware(session):
global exitcode
firmware_shown = False
nodes_matched = False
for component in components:
for res in session.read(
'/noderange/{0}/inventory/firmware/all/{1}'.format(
noderange, component)):
nodes_matched = True
exitcode |= client.printerror(res)
if 'databynode' not in res:
continue
@@ -161,7 +163,9 @@ def show_firmware(session):
for prefix in inv:
firmware_shown = True
printfirm(node, prefix, inv[prefix])
if not firmware_shown and not exitcode:
if not nodes_matched:
sys.stderr.write('No matching nodes for noderange "{0}"'.format(noderange))
elif not firmware_shown and not exitcode:
argparser.print_help()
+1 -1
View File
@@ -36,7 +36,7 @@ exitcode = 0
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][install <file>|save <directory>|delete <name>]")
"%prog <noderange> [list][install <filename>|save <directory>|delete <name>]")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to delete licenses from, '
+2 -2
View File
@@ -32,7 +32,7 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange "
usage="Usage: %prog [options] <noderange> "
"([status|on|off|shutdown|boot|reset])")
argparser.add_option('-p', '--showprevious', dest='previous',
action='store_true', default=False,
@@ -77,4 +77,4 @@ if options.previous:
# add dictionary to session
session.add_precede_dict(prev)
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
+1 -1
View File
@@ -33,7 +33,7 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog noderange
usage='''\n %prog <noderange>
\n ''')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
+1 -1
View File
@@ -40,7 +40,7 @@ import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog location noderange:location",
usage="Usage: %prog <file/directorylist> <noderange>:<destination>",
)
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
+1 -1
View File
@@ -39,7 +39,7 @@ import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange commandexpression",
usage="Usage: %prog [options] <noderange> <command expression>",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
+3 -3
View File
@@ -507,7 +507,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
printattr)
else:
attrout = '{0}: {1}:'.format(node, printattr)
elif 'broken' in currattr:
elif isinstance(currattr, dict) and 'broken' in currattr:
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
'{2}'.format(node, printattr,
currattr['broken'])
@@ -524,7 +524,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
blame = options.blame
except AttributeError:
blame = False
if blame or 'broken' in currattr:
if blame or (isinstance(currattr, dict) and 'broken' in currattr):
blamedata = []
if 'inheritedfrom' in currattr:
blamedata.append('inherited from group {0}'.format(
@@ -633,7 +633,7 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
attrout = '{0}: {1}: ********'.format(noderange, attr)
else:
attrout = '{0}: {1}:'.format(noderange, attr)
elif 'broken' in currattr:
elif isinstance(currattr, dict) and 'broken' in currattr:
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
'{2}'.format(noderange, attr,
currattr['broken'])
+35 -3
View File
@@ -156,6 +156,40 @@ _confluent_osimage_completion()
fi
}
_confluent_imgutil_completion()
{
_confluent_get_args
if [ $NUMARGS == 2 ]; then
COMPREPLY=($(compgen -W "build exec unpack pack capture" -- ${COMP_WORDS[COMP_CWORD]}))
return
elif [ ${CMPARGS[1]} == 'build' ]; then
COMPREPLY=($(compgen -W "-s $(ls /var/lib/confluent/distributions)" -- ${COMP_WORDS[COMP_CWORD]}))
return
elif [ ${CMPARGS[1]} == 'unpack' ]; then
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "-s $(ls /var/lib/confluent/public/os)" -- ${COMP_WORDS[COMP_CWORD]}))
return
fi
compopt -o dirnames
return
elif [ ${CMPARGS[1]} == 'pack' ]; then
if [ $NUMARGS == 3 ]; then
compopt -o dirnames
fi
COMPREPLY=()
return
elif [ ${CMPARGS[1]} == 'capture' ]; then
if [ $NUMARGS == 3 ]; then
_confluent_nn_completion
return
fi
return
elif [ ${CMPARGS[1]} == 'exec' ]; then
compopt -o dirnames
COMPREPLY=($(compgen -W "-v" -- ${COMP_WORDS[COMP_CWORD]}))
return
fi
}
_confluent_nodedeploy_completion()
{
_confluent_get_args
@@ -238,9 +272,6 @@ _confluent_nodeattrib_completion()
_confluent_nn_completion()
{
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
@@ -302,6 +333,7 @@ complete -F _confluent_nr_completion nodeeventlog
complete -F _confluent_nodefirmware_completion nodefirmware
complete -F _confluent_nodedeploy_completion nodedeploy
complete -F _confluent_osimage_completion osdeploy
complete -F _confluent_imgutil_completion imgutil
complete -F _confluent_ng_completion nodegroupattrib
complete -F _confluent_ng_completion nodegroupremove
complete -F _confluent_nr_completion nodehealth
+2 -2
View File
@@ -6,8 +6,8 @@ collective(1) -- Check and manage a confluent collective
`collective invite <server>`
`collective join <server> [-i TOKEN]`
`collective show`
`collective gencert`
`collective delete`
`collective gencert`
`collective delete`
## DESCRIPTION
@@ -15,7 +15,7 @@ the json files (password protected, removed from the files, or unprotected).
## OPTIONS
* `-p`, `--password`:
* `-p PASSWORD`, `--password=PASSWORD`:
If specified, information such as usernames and passwords will be encrypted
using the given password.
+2 -2
View File
@@ -27,7 +27,7 @@ actually be in effect until a reboot.
If combined with `-x`, will show all differing values except those indicated
by `-x`
* `-b`, `--batch`:
* `-b settings.batch`, `--batch=settings.batch`:
Provide arguments as lines of a file, rather than the command line.
* `-d`, `--detail`:
@@ -47,7 +47,7 @@ actually be in effect until a reboot.
Include advanced settings, which are normally not intended to be used
without direction from the relevant server vendor.
* `-r`, `--restoredefault`:
* `-r COMPONENT`, `--restoredefault=COMPONENT`:
Request that the specified component of the targeted nodes will have its
configuration reset to default. Currently the only component implemented
is uefi.
+1 -1
View File
@@ -3,7 +3,7 @@ nodelicense(8) -- Manage license keys on BMC
## SYNOPSIS
`nodelicense <noderange> [list|install <filename>|delete <license>|save <directory>]`
`nodelicense <noderange> [list][install <filename>|save <directory>|delete <license>]`
## DESCRIPTION
+2 -3
View File
@@ -3,8 +3,7 @@ nodepower(8) -- Check or change power state of confluent nodes
## SYNOPSIS
`nodepower <noderange>`
`nodepower <noderange> [-p] [on|off|boot|shutdown|reset|status]`
`nodepower [options] <noderange> ([status|on|off|shutdown|boot|reset])`
## DESCRIPTION
@@ -28,7 +27,7 @@ off will not react to this request.
## OPTIONS
* `-p`, '--showprevious':
* `-p`, `--showprevious`:
Show previous power state for all directives that may change power state.
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
+1 -1
View File
@@ -12,7 +12,7 @@ noderange. This will present progress as percentage for all nodes.
## OPTIONS
* `-c`, `-f`, `--count`:
* `-f COUNT`, `-c COUNT`, `--count=COUNT`:
Specify how many rsync executions to do concurrently. If noderange
exceeds the count, then excess nodes will wait until one of the
active count completes.
+2 -2
View File
@@ -3,7 +3,7 @@ noderun(8) -- Run arbitrary commands per node in a noderange
## SYNOPSIS
`noderun <noderange> <command string>`
`noderun [options] <noderange> <command expression>`
## DESCRIPTION
@@ -15,7 +15,7 @@ themselves, see nodeshell(8).
## OPTIONS
* `-f`, `-c`, `--count`:
* `-f COUNT`, `-c COUNT`, `--count=COUNT`:
Number of commands to run at a time
* `-n`, `--nonodeprefix`:
@@ -8,34 +8,32 @@ import socket
import subprocess
import ssl
import sys
import time
class InvalidApiKey(Exception):
pass
def get_apikey(nodename, mgr):
sealnew = True
apikey = ""
if os.path.exists('/etc/confluent/confluent.apikey'):
return open('/etc/confluent/confluent.apikey').read().strip()
apikey = subprocess.check_output(['/opt/confluent/bin/clortho', nodename, mgr])
if not isinstance(apikey, str):
apikey = apikey.decode('utf8')
if apikey.startswith('SEALED:'):
sealnew = False
with open('/etc/confluent/confluent.sealedapikey', 'w+') as apiout:
apiout.write(apikey[7:])
with open('/etc/confluent/confluent.sealedapikey') as inp:
sp = subprocess.Popen(['/usr/bin/clevis-decrypt-tpm2'],
stdin=inp, stdout=subprocess.PIPE)
apikey = sp.communicate()[0]
if not isinstance(apikey, str):
apikey = apikey.decode('utf8')
apikey = open('/etc/confluent/confluent.apikey').read().strip()
if apikey:
return apikey
while not apikey:
try:
apikey = subprocess.check_output(['/opt/confluent/bin/clortho', nodename, mgr])
except subprocess.CalledProcessError:
apikey = ''
if not isinstance(apikey, str):
apikey = apikey.decode('utf8')
if apikey.startswith('SEALED:'):
apikey = ""
if not apikey:
sys.stderr.write(
"Failed getting API token, check deployment.apiarmed attribute on {}\n".format(nodename))
time.sleep(10)
with open('/etc/confluent/confluent.apikey', 'w+') as apiout:
apiout.write(apikey)
if sealnew and os.path.exists('/usr/bin/clevis-encrypt-tpm2'):
try:
with open('/etc/confluent/confluent.apikey') as apin:
sealed = subprocess.check_output(
['/usr/bin/clevis-encrypt-tpm2', '{}'], stdin=apin)
print(HTTPSClient().grab_url('/confluent-api/self/saveapikey', sealed).decode())
except Exception:
sys.stderr.write('Unable to persist API key through TPM2 sealing\n')
apikey = apikey.strip()
os.chmod('/etc/confluent/confluent.apikey', 0o600)
return apikey
@@ -79,6 +77,8 @@ class HTTPSClient(client.HTTPConnection, object):
if mgtiface:
self.stdheaders['CONFLUENT_MGTIFACE'] = mgtiface
client.HTTPConnection.__init__(self, host, port)
self.host = host
self.node = node
self.connect()
def set_header(self, key, val):
@@ -107,13 +107,23 @@ class HTTPSClient(client.HTTPConnection, object):
method = 'POST'
else:
method = 'GET'
self.request(method, url, data, headers=self.stdheaders)
rsp = self.getresponse()
if rsp.status >= 200 and rsp.status < 300:
if returnrsp:
return rsp.status, rsp
else:
return rsp.status, rsp.read()
authed = False
while not authed:
authed = True
self.request(method, url, data, headers=self.stdheaders)
rsp = self.getresponse()
if rsp.status >= 200 and rsp.status < 300:
if returnrsp:
return rsp.status, rsp
else:
return rsp.status, rsp.read()
if rsp.status == 401:
authed = False
rsp.read()
with open('/etc/confluent/confluent.apikey', 'w+') as akfile:
akfile.write('')
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(
self.node, self.host)
raise Exception(rsp.read())
if __name__ == '__main__':
@@ -4,7 +4,7 @@ Release: 1
Summary: OS Deployment support for confluent
License: Apache2
URL: http://hpc.lenovo.com/
URL: https://hpc.lenovo.com/
Source0: confluent_osdeploy.tar.xz
BuildArch: noarch
Requires: confluent_ipxe mtools tar
@@ -21,11 +21,13 @@ This contains support utilities for enabling deployment of x86_64 architecture s
%build
mkdir -p opt/confluent/bin
mkdir -p stateless-bin
cd utils
make all
cp copernicus clortho autocons ../opt/confluent/bin
cp confluent_imginfo copernicus clortho autocons ../opt/confluent/bin
cp start_root urlmount ../stateless-bin/
cd ..
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04; do
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 coreos; do
mkdir ${os}out
cd ${os}out
cp -a ../opt .
@@ -35,6 +37,17 @@ for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04; do
mv ../addons.cpio .
cd ..
done
for os in el8 suse15; do
mkdir ${os}disklessout
cd ${os}disklessout
cp -a ../opt .
cp -a ../${os}-diskless/initramfs/* .
cp -a ../common/* .
cp -a ../stateless-bin/* opt/confluent/bin
find . | cpio -H newc -o > ../addons.cpio
mv ../addons.cpio .
cd ..
done
mkdir esxi7out
cd esxi7out
cp -a ../opt .
@@ -48,13 +61,19 @@ cp -a esxi7out esxi6out
cp -a esxi7 esxi6
%install
for os in rhvh4 el7 el8 genesis suse15 ubuntu20.04 esxi6 esxi7; do
for os in rhvh4 el7 el8 genesis suse15 ubuntu20.04 esxi6 esxi7 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
cp -a $os/profiles/* %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
find %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles -name .gitignore -exec rm -f {} +
if [ -d ${os}disklessout ]; then
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/profiles
cp ${os}disklessout/addons.* %{buildroot}/opt/confluent/lib/osdeploy/${os}-diskless/initramfs
cp -a ${os}-diskless/profiles/* %{buildroot}/opt/confluent/lib/osdeploy/$os-diskless/profiles
fi
done
find %{buildroot}/opt/confluent/lib/osdeploy/ -name .gitignore -exec rm -f {} +
%files
/opt/confluent/lib/osdeploy
@@ -0,0 +1 @@
/usr/lib/systemd/system/confluent-rootfs.service
@@ -0,0 +1,106 @@
#!/bin/bash
TRIES=0
oum=$(umask)
umask 0077
mkdir -p /etc/confluent
echo -n > /etc/confluent/confluent.info
umask $oum
cd /sys/class/net
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 60 ]; do
TRIES=$((TRIES + 1))
for currif in *; do
ip link set $currif up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
done
cd /
grep ^EXTMGRINFO: /etc/confluent/confluent.info || return 0 # Do absolutely nothing if no data at all yet
echo -n "" > /tmp/confluent.initq
# restart cmdline
echo -n "" > /etc/cmdline.d/01-confluent.conf
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
cat /tls/*.pem > /etc/confluent/ca.pem
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info|head -n 1 | awk '{print $2}')
if [[ $confluent_mgr == *%* ]]; then
echo $confluent_mgr | awk -F% '{print $2}' > /tmp/confluent.ifidx
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
fi
needseal=1
oldumask=$(umask)
umask 0077
while [ -z "$confluent_apikey" ]; do
/opt/confluent/bin/clortho $nodename $confluent_mgr > /etc/confluent/confluent.apikey
if grep ^SEALED: /etc/confluent/confluent.apikey > /dev/null; then
needseal=0
sed -e s/^SEALED:// /etc/confluent/confluent.apikey | clevis-decrypt-tpm2 > /etc/confluent/confluent.apikey.decrypt
mv /etc/confluent/confluent.apikey.decrypt /etc/confluent/confluent.apikey
fi
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
if [ -z "$confluent_apikey" ]; then
echo "Unable to acquire node api key, no TPM2 sealed nor fresh token available, retrying..."
sleep 10
fi
done
if [[ $confluent_mgr == *:* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
if [ $needseal == 1 ]; then
sealed=$(echo $confluent_apikey | clevis-encrypt-tpm2 {})
if [ ! -z "$sealed" ]; then
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d $sealed https://$confluent_mgr/confluent-api/self/saveapikey
fi
fi
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg
umask $oldumask
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg)
confluent_profile=${confluent_profile#profile: }
if [ "$autoconfigmethod" = "dhcp" ]; then
echo ip=$ifname:dhcp >> /etc/cmdline.d/01-confluent.conf
else
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ipv4_netmask: /etc/confluent/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 < /etc/confluent/confluent.deploycfg
if [ -e /lib/nm-lib.sh ]; then
. /lib/nm-lib.sh
nm_generate_connections
if [[ "$ifname" == ib* ]]; then
sed -i s/type=ethernet/type=infiniband/ /run/NetworkManager/system-connections/$ifname.nmconnection
if ! grep '\[infiniband\]' /run/NetworkManager/system-connections/$ifname.nmconnection > /dev/null; then
echo >> /run/NetworkManager/system-connections/$ifname.nmconnection
echo '[infiniband]' >> /run/NetworkManager/system-connections/$ifname.nmconnection
echo transport-mode=datagram >> /run/NetworkManager/system-connections/$ifname.nmconnection
fi
fi
fi
cat /proc/cmdline /etc/cmdline.d/01-confluent.conf | tr '\n' ' ' > /run/fakecmdline
mount -o bind /run/fakecmdline /proc/cmdline
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/rootfs.img | rdcore stream-hash /etc/coreos-live-want-rootfs | bsdtar -xf - -C /
@@ -0,0 +1,10 @@
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
fi
fi
@@ -0,0 +1,28 @@
#!/bin/sh
# if ignition configuration is driving, defer to it
if [ ! -f /config.ign ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info| head -n 1| awk '{print $2}' | sed -e s/%/%25/)
if [[ $confluent_mgr = *:* ]]; then
confluent_mgr=[$confluent_mgr]
fi
rootpassword=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg)
rootpassword=${rootpassword#rootpassword: }
if [ "$rootpassword" = "null" ]; then
rootpassword=""
fi
if [ ! -z "$rootpassword" ]; then
sed -i "s@root:[^:]*:@root:$rootpassword:@" /sysroot/etc/shadow
fi
mount -o bind /dev /sysroot/dev
chroot /sysroot ssh-keygen -A
umount /sysroot/dev
for i in /sysroot/etc/ssh/ssh_host*key.pub; do
certname=${i/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname
echo HostKey ${i%.pub} | sed -e 's!/sysroot!!' >> /sysroot/etc/ssh/sshd_config
echo HostCertificate $certname | sed -e 's!/sysroot!!' >> /sysroot/etc/ssh/sshd_config
done
@@ -0,0 +1,19 @@
[Unit]
Description=Confluent initialization
DefaultDependencies=false
After=basic.target
# Network is enabled here
After=dracut-initqueue.service
Before=coreos-livepxe-rootfs.service
# If we fail, the boot will fail. Be explicit about it.
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/opt/confluent/bin/initconfluent.sh
@@ -0,0 +1,8 @@
#!/bin/sh
ln -s $1/images/pxeboot/vmlinuz $2/boot/kernel && \
ln -s $1/images/pxeboot/initrd.img $2/boot/initramfs/distribution && \
ln -s $1/images/ignition.img $2/boot/initramfs/ignition.img && \
mkdir -p $2/boot/efi/boot/ && \
ln -s $1/images/pxeboot/rootfs.img $2/ && \
mcopy -i $1/images/efiboot.img ::efi/redhat/grubx64.efi $2/boot/efi/boot/ && \
mcopy -i $1/images/efiboot.img ::efi/boot/bootx64.efi $2/boot/efi/boot/
@@ -0,0 +1,5 @@
label: RedHat CoreOS %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet random.trust_cpu=on ignition.firstboot ignition.platform.id=metal
#the above boots and will have ignition agent phone home and await instructions
#kernelargs: quiet random.trust_cpu=on ignition.firstboot ignition.platform.id=metal coreos.inst=yes coreos.inst.install_dev=sda
#the above runs the installer to write to disk
@@ -35,10 +35,11 @@ reboot
%packages
#-kernel-uek # This can opt out of the UEK for the relevant distribution
chrony
rsync
python
tar
pciutils
python
rsync
tar
-iwl*-firmware
%include /tmp/addonpackages
%end
@@ -11,6 +11,9 @@ confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{prin
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
@@ -1,4 +1,7 @@
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
@@ -51,7 +54,7 @@ source_remote() {
chmod +x $1
cmd=$1
shift
source $cmd
source ./$cmd
cd - > /dev/null
return $retcode
}
@@ -12,6 +12,8 @@ export confluent_mgr confluent_profile nodename
cp -a /etc/confluent /mnt/sysimage/etc
chmod -R og-rwx /mnt/sysimage/etc/confluent
cp /tmp/functions /mnt/sysimage/etc/confluent/
hostnamectl set-hostname $nodename
cp /etc/hostname /mnt/sysimage/etc/hostname
. /tmp/functions
if [ -f /tmp/cryptboot ]; then
cp /tmp/cryptboot /mnt/sysimage/tmp/
@@ -0,0 +1,240 @@
get_remote_apikey() {
while [ -z "$confluent_apikey" ]; do
/opt/confluent/bin/clortho $nodename $confluent_mgr > /etc/confluent/confluent.apikey
if grep ^SEALED: /etc/confluent/confluent.apikey > /dev/null; then
# we don't support remote sealed api keys anymore
echo > /etc/confluent/confluent.apikey
fi
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
if [ -z "$confluent_apikey" ]; then
echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..."
sleep 10
else
tmpdir=$(mktemp -d)
cd $tmpdir
tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
tpm2_createprimary -G ecc -Q --key-context=prim.ctx
(echo -n "CONFLUENT_APIKEY:";cat /etc/confluent/confluent.apikey) | tpm2_create -Q --policy=pcr15.sha256.policy --public=data.pub --private=data.priv -i - -C prim.ctx
tpm2_load -Q --parent-context=prim.ctx --public=data.pub --private=data.priv --name=confluent.apikey --key-context=data.ctx
tpm2_evictcontrol -Q -c data.ctx
tpm2_flushcontext session.ctx
cd - > /dev/null
rm -rf $tmpdir
fi
done
}
root=1
rootok=1
netroot=confluent
clear
mkdir -p /etc/ssh
mkdir -p /var/tmp/
mkdir -p /var/empty/sshd
mkdir -p /etc/confluent
sed -i '/^root:x/d' /etc/passwd
echo root:x:0:0::/:/bin/bash >> /etc/passwd
echo sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin >> /etc/passwd
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
autocons=${autocons##*/}
echo "Automatic console configured for $autocons"
fi
echo "Initializing confluent diskless environment"
echo -n "udevd: "
/usr/lib/systemd/systemd-udevd --daemon
echo -n "Loading drivers..."
udevadm trigger
udevadm trigger --type=devices --action=add
udevadm settle
modprobe ib_ipoib
modprobe ib_umad
modprobe hfi1
modprobe mlx5_ib
echo "done"
cat > /etc/ssh/sshd_config << EOF
Port 2222
Subsystem sftp /usr/libexec/openssh/sftp-server
PermitRootLogin yes
AuthorizedKeysFile .ssh/authorized_keys
EOF
mkdir /root/.ssh
mkdir /.ssh
cat /ssh/*pubkey > /root/.ssh/authorized_keys 2>/dev/null
cp /root/.ssh/authorized_keys /.ssh/
cat /tls/*.pem > /etc/confluent/ca.pem
mkdir -p /etc/pki/tls/certs
cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
TRIES=0
oldumask=$(umask)
umask 0077
tpmdir=$(mktemp -d)
cd $tpmdir
lasthdl=""
for hdl in $(tpm2_getcap handles-persistent|awk '{print $2}'); do
tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
unsealeddata=$(tpm2_unseal --auth=session:session.ctx -Q -c $hdl 2>/dev/null)
tpm2_flushcontext session.ctx
if [[ $unsealeddata == "CONFLUENT_APIKEY:"* ]]; then
confluent_apikey=${unsealeddata#CONFLUENT_APIKEY:}
echo $confluent_apikey > /etc/confluent/confluent.apikey
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
lasthdl=$hdl
fi
done
cd - > /dev/null
rm -rf $tpmdir
touch /etc/confluent/confluent.info
cd /sys/class/net
echo -n "Scanning for network configuration..."
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 30 ]; do
TRIES=$((TRIES + 1))
for i in *; do
ip link set $i up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
done
cd /
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
hostname $nodename
confluent_mgr=$(grep '^EXTMGRINFO:.*1$' /etc/confluent/confluent.info | head -n 1 | awk -F': ' '{print $2}' | awk -F'|' '{print $1}')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info|head -n 1 | awk '{print $2}')
fi
if [[ $confluent_mgr == *%* ]]; then
echo $confluent_mgr | awk -F% '{print $2}' > /tmp/confluent.ifidx
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
fi
ready=0
while [ $ready = "0" ]; do
get_remote_apikey
if [[ $confluent_mgr == *:* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
tmperr=$(mktemp)
curl -sSf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg 2> $tmperr
if grep 401 $tmperr > /dev/null; then
confluent_apikey=""
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
else
ready=1
fi
rm $tmperr
done
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2
umask $oldumask
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "dhcp" ]; then
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
echo "Complete:"
ip addr show dev $ifname
else
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^prefix: /etc/confluent/confluent.deploycfg)
v4nm=${v4nm#prefix: }
echo "Setting up $ifname as static at $v4addr/$v4nm"
ip addr add dev $ifname $v4addr/$v4nm
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
mkdir -p /run/NetworkManager/system-connections
cat > /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
[connection]
EOC
echo id=${ifname} >> /run/NetworkManager/system-connections/$ifname.nmconnection
echo uuid=$(uuidgen) >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
type=ethernet
autoconnect-retries=1
EOC
echo interface-name=$ifname >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
multi-connect=1
permissions=
wait-device-timeout=60000
[ethernet]
mac-address-blacklist=
[ipv4]
EOC
echo address1=$v4addr/$v4nm >> /run/NetworkManager/system-connections/$ifname.nmconnection
if [ ! -z "$v4gw" ]; then
echo gateway=$v4gw >> /run/NetworkManager/system-connections/$ifname.nmconnection
fi
nameserversec=0
nameservers=""
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]]; then
nameservers="$nameservers"${entry#- }";"
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /etc/confluent/confluent.deploycfg
echo dns=$nameservers >> /run/NetworkManager/system-connections/$ifname.nmconnection
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
echo dns-search=$dnsdomain >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
may-fail=false
method=manual
[ipv6]
addr-gen-mode=eui64
method=auto
[proxy]
EOC
fi
chmod 600 /run/NetworkManager/system-connections/*.nmconnection
echo -n "Initializing ssh..."
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /etc/ssh/sshd_config
fi
echo HostKey $privfile >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_urls=""
for addr in $(grep ^MANAGER: /etc/confluent/confluent.info|awk '{print $2}'|sed -e s/%/%25/); do
if [[ $addr == *:* ]]; then
confluent_urls="$confluent_urls $confluent_proto://[$addr]/confluent-public/os/$confluent_profile/rootimg.sfs"
else
confluent_urls="$confluent_urls $confluent_proto://$addr/confluent-public/os/$confluent_profile/rootimg.sfs"
fi
done
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg| awk '{print $2}')
mkdir -p /etc/confluent
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
. /etc/confluent/functions
source_remote imageboot.sh
@@ -0,0 +1,4 @@
. /etc/confluent/functions
# This is a convenient place to keep customizations separate from modifying the stock scripts
# While modification of the stock scripts is fine, it may be easier to rebase to a newer
# stock profile if the '.custom' files are used.
@@ -0,0 +1,11 @@
[Unit]
Description=First Boot Process
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/firstboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,39 @@
#!/bin/sh
# This script is executed on the first boot after install has
# completed. It is best to edit the middle of the file as
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
logshowpid=$!
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
run_remote firstboot.custom
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
run_remote_parts firstboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot.d
fi
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
systemctl disable firstboot
rm /etc/systemd/system/firstboot.service
rm /etc/confluent/firstboot.ran
kill $logshowpid
@@ -0,0 +1,144 @@
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
curlargs=""
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(/usr/libexec/platform-python $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(/usr/libexec/platform-python $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source ./$cmd
cd - > /dev/null
return $retcode
}
run_remote() {
requestedcmd="'$*'"
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
if [ -x /usr/bin/chcon ]; then
chcon system_u:object_r:bin_t:s0 $cmd
fi
shift
./$cmd $*
retcode=$?
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
return $retcode
}
run_remote_python() {
echo
set_confluent_vars
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
else
/usr/bin/python $*
fi
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
return $retcode
}
run_remote_config() {
echo
set_confluent_vars
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
else
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/status -w 204
fi
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
@@ -0,0 +1,93 @@
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")
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2', '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,376 @@
#!/usr/bin/python3
import glob
import json
import os
import re
import time
import shutil
import socket
import stat
import struct
import sys
import subprocess
def get_next_part_meta(img, imgsize):
if img.tell() == imgsize:
return None
pathlen = struct.unpack('!H', img.read(2))[0]
mountpoint = img.read(pathlen).decode('utf8')
jsonlen = struct.unpack('!I', img.read(4))[0]
metadata = json.loads(img.read(jsonlen).decode('utf8'))
img.seek(16, 1) # skip the two 64-bit values we don't use, they are in json
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip filesystem type
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip orig devname (redundant with json)
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip padding
nextlen = struct.unpack('!Q', img.read(8))[0]
img.seek(nextlen, 1) # go to next section
return metadata
def get_multipart_image_meta(img):
img.seek(0, 2)
imgsize = img.tell()
img.seek(16)
seekamt = img.read(1)
img.seek(struct.unpack('B', seekamt)[0], 1)
partinfo = get_next_part_meta(img, imgsize)
while partinfo:
yield partinfo
partinfo = get_next_part_meta(img, imgsize)
def get_image_metadata(imgpath):
with open(imgpath, 'rb') as img:
header = img.read(16)
if header == b'\x63\x7b\x9d\x26\xb7\xfd\x48\x30\x89\xf9\x11\xcf\x18\xfd\xff\xa1':
for md in get_multipart_image_meta(img):
yield md
else:
raise Exception('Installation from single part image not supported')
class PartedRunner():
def __init__(self, disk):
self.disk = disk
def run(self, command):
command = command.split()
command = ['parted', '-a', 'optimal', '-s', self.disk] + command
return subprocess.check_output(command).decode('utf8')
def fixup(rootdir, vols):
devbymount = {}
for vol in vols:
devbymount[vol['mount']] = vol['targetdisk']
fstabfile = os.path.join(rootdir, 'etc/fstab')
with open(fstabfile) as tfile:
fstab = tfile.read().split('\n')
while not fstab[0]:
fstab = fstab[1:]
if os.path.exists(os.path.join(rootdir, '.autorelabel')):
os.unlink(os.path.join(rootdir, '.autorelabel'))
with open(fstabfile, 'w') as tfile:
for tab in fstab:
entry = tab.split()
if tab.startswith('#ORIGFSTAB#'):
if entry[1] in devbymount:
targetdev = devbymount[entry[1]]
if targetdev.startswith('/dev/localstorage/'):
entry[0] = targetdev
else:
uuid = subprocess.check_output(['blkid', '-s', 'UUID', '-o', 'value', targetdev]).decode('utf8')
uuid = uuid.strip()
entry[0] = 'UUID={}'.format(uuid)
elif entry[2] == 'swap':
entry[0] = '/dev/mapper/localstorage-swap'
entry[0] = entry[0].ljust(42)
entry[1] = entry[1].ljust(16)
entry[3] = entry[3].ljust(28)
tab = '\t'.join(entry)
tfile.write(tab + '\n')
with open(os.path.join(rootdir, 'etc/hostname'), 'w') as nameout:
nameout.write(socket.gethostname() + '\n')
selinuxconfig = os.path.join(rootdir, 'etc/selinux/config')
policy = None
if os.path.exists(selinuxconfig):
with open(selinuxconfig) as cfgin:
sec = cfgin.read().split('\n')
for l in sec:
l = l.split('#', 1)[0]
if l.startswith('SELINUXTYPE='):
_, policy = l.split('=')
for sshkey in glob.glob(os.path.join(rootdir, 'etc/ssh/*_key*')):
os.unlink(sshkey)
for sshkey in glob.glob('/etc/ssh/*_key*'):
newkey = os.path.join(rootdir, sshkey[1:])
shutil.copy2(sshkey, newkey)
finfo = os.stat(sshkey)
os.chown(newkey, finfo[stat.ST_UID], finfo[stat.ST_GID])
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/sysconfig/network-scripts/*')):
os.unlink(ifcfg)
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/NetworkManager/system-connections/*')):
os.unlink(ifcfg)
for ifcfg in glob.glob('/run/NetworkManager/system-connections/*'):
newcfg = ifcfg.split('/')[-1]
newcfg = os.path.join(rootdir, 'etc/NetworkManager/system-connections/{0}'.format(newcfg))
shutil.copy2(ifcfg, newcfg)
shutil.rmtree(os.path.join(rootdir, 'etc/confluent/'))
shutil.copytree('/etc/confluent', os.path.join(rootdir, 'etc/confluent'))
if policy:
sys.stdout.write('Applying SELinux labeling...')
sys.stdout.flush()
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'etc')])
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'opt')])
sys.stdout.write('Done\n')
sys.stdout.flush()
for metafs in ('proc', 'sys', 'dev'):
subprocess.check_call(['mount', '-o', 'bind', '/{}'.format(metafs), os.path.join(rootdir, metafs)])
with open(os.path.join(rootdir, 'etc/sysconfig/grub')) as defgrubin:
defgrub = defgrubin.read().split('\n')
with open(os.path.join(rootdir, 'etc/sysconfig/grub'), 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
newline = []
for ent in gline:
if ent.startswith('resume=') or ent.startswith('rd.lvm.lv'):
continue
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/')
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
newroot = None
with open('/etc/shadow') as shadowin:
shents = shadowin.read().split('\n')
for shent in shents:
shent = shent.split(':')
if not shent:
continue
if shent[0] == 'root' and shent[1] not in ('*', '!!', ''):
newroot = shent[1]
if newroot:
shlines = None
with open(os.path.join(rootdir, 'etc/shadow')) as oshadow:
shlines = oshadow.read().split('\n')
with open(os.path.join(rootdir, 'etc/shadow'), 'w') as oshadow:
for line in shlines:
if line.startswith('root:'):
line = line.split(':')
line[1] = newroot
line = ':'.join(line)
oshadow.write(line + '\n')
partnum = None
targblock = None
for vol in vols:
if vol['mount'] == '/boot/efi':
targdev = vol['targetdisk']
partnum = re.search('(\d+)$', targdev).group(1)
targblock = re.search('(.*)\d+$', targdev).group(1)
if targblock:
shimpath = subprocess.check_output(['find', os.path.join(rootdir, 'boot/efi'), '-name', 'shimx64.efi']).decode('utf8').strip()
shimpath = shimpath.replace(rootdir, '/').replace('/boot/efi', '').replace('//', '/').replace('/', '\\')
subprocess.check_call(['efibootmgr', '-c', '-d', targblock, '-l', shimpath, '--part', partnum])
#other network interfaces
def had_swap():
with open('/etc/fstab') as tabfile:
tabs = tabfile.read().split('\n')
for tab in tabs:
tab = tab.split()
if len(tab) < 3:
continue
if tab[2] == 'swap':
return True
return False
def install_to_disk(imgpath):
lvmvols = {}
deftotsize = 0
mintotsize = 0
deflvmsize = 0
minlvmsize = 0
biggestsize = 0
biggestfs = None
plainvols = {}
allvols = []
swapsize = 0
if had_swap():
with open('/proc/meminfo') as meminfo:
swapsize = meminfo.read().split('\n')[0]
swapsize = int(swapsize.split()[1])
if swapsize < 2097152:
swapsize = swapsize * 2
elif swapsize > 8388608 and swapsize < 67108864:
swapsize = swapsize * 0.5
elif swapsize >= 67108864:
swapsize = 33554432
swapsize = int(swapsize * 1024)
deftotsize = swapsize
mintotsize = swapsize
for fs in get_image_metadata(imgpath):
allvols.append(fs)
deftotsize += fs['initsize']
mintotsize += fs['minsize']
if fs['initsize'] > biggestsize:
biggestfs = fs
biggestsize = fs['initsize']
if fs['device'].startswith('/dev/mapper'):
lvmvols[fs['device'].replace('/dev/mapper/', '')] = fs
deflvmsize += fs['initsize']
minlvmsize += fs['minsize']
else:
plainvols[int(re.search('(\d+)$', fs['device'])[0])] = fs
with open('/tmp/installdisk') as diskin:
instdisk = diskin.read()
instdisk = '/dev/' + instdisk
parted = PartedRunner(instdisk)
dinfo = parted.run('unit s print')
dinfo = dinfo.split('\n')
sectors = 0
sectorsize = 0
for inf in dinfo:
if inf.startswith('Disk {0}:'.format(instdisk)):
_, sectors = inf.split(': ')
sectors = int(sectors.replace('s', ''))
if inf.startswith('Sector size (logical/physical):'):
_, sectorsize = inf.split(':')
sectorsize = sectorsize.split('/')[0]
sectorsize = sectorsize.replace('B', '')
sectorsize = int(sectorsize)
# for now, only support resizing/growing the largest partition
minexcsize = deftotsize - biggestfs['initsize']
mintotsize = deftotsize - biggestfs['initsize'] + biggestfs['minsize']
minsectors = mintotsize // sectorsize
if sectors < (minsectors + 65536):
raise Exception('Disk too small to fit image')
biggestsectors = sectors - (minexcsize // sectorsize)
biggestsize = sectorsize * biggestsectors
parted.run('mklabel gpt')
curroffset = 2048
for volidx in sorted(plainvols):
vol = plainvols[volidx]
if vol is not biggestfs:
size = vol['initsize'] // sectorsize
else:
size = biggestsize // sectorsize
size += 2047 - (size % 2048)
end = curroffset + size
if end > sectors:
end = sectors
parted.run('mkpart primary {}s {}s'.format(curroffset, end))
vol['targetdisk'] = instdisk + '{0}'.format(volidx)
curroffset += size + 1
if not lvmvols:
if swapsize:
swapsize = swapsize // sectorsize
swapsize += 2047 - (size % 2048)
end = curroffset + swapsize
if end > sectors:
end = sectors
parted.run('mkpart swap {}s {}s'.format(curroffset, end))
subprocess.check_call(['mkswap', instdisk + '{}'.format(volidx + 1)])
else:
parted.run('mkpart lvm {}s 100%'.format(curroffset))
lvmpart = instdisk + '{}'.format(volidx + 1)
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
subprocess.check_call(['vgcreate', 'localstorage', lvmpart])
vginfo = subprocess.check_output(['vgdisplay', 'localstorage', '--units', 'b']).decode('utf8')
vginfo = vginfo.split('\n')
pesize = 0
pes = 0
for infline in vginfo:
infline = infline.split()
if len(infline) >= 3 and infline[:2] == ['PE', 'Size']:
pesize = int(infline[2])
if len(infline) >= 5 and infline[:2] == ['Free', 'PE']:
pes = int(infline[4])
takeaway = swapsize // pesize
for volidx in lvmvols:
vol = lvmvols[volidx]
if vol is biggestfs:
continue
takeaway += vol['initsize'] // pesize
takeaway += 1
biggestextents = pes - takeaway
for volidx in lvmvols:
vol = lvmvols[volidx]
if vol is biggestfs:
extents = biggestextents
else:
extents = vol['initsize'] // pesize
extents += 1
if vol['mount'] == '/':
lvname = 'root'
else:
lvname = vol['mount'].replace('/', '_')
subprocess.check_call(['lvcreate', '-l', '{}'.format(extents), '-y', '-n', lvname, 'localstorage'])
vol['targetdisk'] = '/dev/localstorage/{}'.format(lvname)
if swapsize:
subprocess.check_call(['lvcreate', '-y', '-l', '{}'.format(swapsize // pesize), '-n', 'swap', 'localstorage'])
subprocess.check_call(['mkswap', '/dev/localstorage/swap'])
os.makedirs('/run/imginst/targ')
for vol in allvols:
with open(vol['targetdisk'], 'wb') as partition:
partition.write(b'\x00' * 1 * 1024 * 1024)
subprocess.check_call(['mkfs.{}'.format(vol['filesystem']), vol['targetdisk']])
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ'])
source = vol['mount'].replace('/', '_')
source = '/run/imginst/sources/' + source
blankfsstat = os.statvfs('/run/imginst/targ')
blankused = (blankfsstat.f_blocks - blankfsstat.f_bfree) * blankfsstat.f_bsize
sys.stdout.write('\nWriting {0}: '.format(vol['mount']))
with subprocess.Popen(['cp', '-ax', source + '/.', '/run/imginst/targ']) as copier:
stillrunning = copier.poll()
lastprogress = 0.0
while stillrunning is None:
currfsstat = os.statvfs('/run/imginst/targ')
currused = (currfsstat.f_blocks - currfsstat.f_bfree) * currfsstat.f_bsize
currused -= blankused
with open('/proc/meminfo') as meminf:
for line in meminf.read().split('\n'):
if line.startswith('Dirty:'):
_, dirty, _ = line.split()
dirty = int(dirty) * 1024
progress = (currused - dirty) / vol['minsize']
if progress < lastprogress:
progress = lastprogress
if progress > 0.99:
progress = 0.99
lastprogress = progress
progress = progress * 100
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
sys.stdout.flush()
time.sleep(0.5)
stillrunning = copier.poll()
if stillrunning != 0:
raise Exception("Error copying volume")
with subprocess.Popen(['sync']) as syncrun:
stillrunning = syncrun.poll()
while stillrunning is None:
with open('/proc/meminfo') as meminf:
for line in meminf.read().split('\n'):
if line.startswith('Dirty:'):
_, dirty, _ = line.split()
dirty = int(dirty) * 1024
progress = (vol['minsize'] - dirty) / vol['minsize']
if progress < lastprogress:
progress = lastprogress
if progress > 0.99:
progress = 0.99
lastprogress = progress
progress = progress * 100
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
sys.stdout.flush()
time.sleep(0.5)
stillrunning = syncrun.poll()
sys.stdout.write('\x1b[1K\rDone writing {0}'.format(vol['mount']))
sys.stdout.write('\n')
sys.stdout.flush()
subprocess.check_call(['umount', '/run/imginst/targ'])
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
fixup('/run/imginst/targ', allvols)
if __name__ == '__main__':
install_to_disk(os.environ['mountsrc'])
@@ -0,0 +1,121 @@
. /lib/dracut-lib.sh
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs"
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
fi
/opt/confluent/bin/confluent_imginfo /mnt/remoteimg/rootimg.sfs > /tmp/rootimg.info
loopdev=$(losetup -f)
export mountsrc=$loopdev
losetup -r $loopdev /mnt/remoteimg/rootimg.sfs
if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/profileprivate/pending/rootimg.key > /tmp/rootimg.key
cipher=$(head -n 1 /tmp/rootimg.key)
key=$(tail -n 1 /tmp/rootimg.key)
len=$(wc -c /mnt/remoteimg/rootimg.sfs | awk '{print $1}')
len=$(((len-4096)/512))
dmsetup create cryptimg --table "0 $len crypt $cipher $key 0 $loopdev 8"
/opt/confluent/bin/confluent_imginfo /dev/mapper/cryptimg > /tmp/rootimg.info
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
. /tmp/mountparts.sh
fi
#mount -t tmpfs overlay /mnt/overlay
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
fi
mkdir -p /sysroot/etc/ssh
mkdir -p /sysroot/etc/confluent
mkdir -p /sysroot/root/.ssh
cp /root/.ssh/* /sysroot/root/.ssh
chmod 700 /sysroot/root/.ssh
cp /etc/confluent/* /sysroot/etc/confluent/
cp /etc/ssh/*key* /sysroot/etc/ssh/
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /sysroot/etc/ssh/sshd_config
fi
echo HostKey $privfile >> /sysroot/etc/ssh/sshd_config
done
mkdir -p /sysroot/dev /sysroot/sys /sysroot/proc /sysroot/run
if [ ! -z "$autocons" ]; then
autocons=${autocons%,*}
mkdir -p /run/systemd/generator/getty.target.wants
ln -s /usr/lib/systemd/system/serial-getty@.service /run/systemd/generator/getty.target.wants/serial-getty@${autocons}.service
fi
while [ ! -e /sysroot/sbin/init ]; do
echo "Failed to access root filesystem or it is missing /sbin/init"
echo "System should be accessible through ssh at port 2222 with the appropriate key"
while [ ! -e /sysroot/sbin/init ]; do
sleep 1
done
done
rootpassword=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg)
rootpassword=${rootpassword#rootpassword: }
if [ "$rootpassword" = "null" ]; then
rootpassword=""
fi
if [ ! -z "$rootpassword" ]; then
sed -i "s@root:[^:]*:@root:$rootpassword:@" /sysroot/etc/shadow
fi
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
echo HostbasedAuthentication yes >> /sysroot/etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /sysroot/etc/ssh/sshd_config
echo IgnoreRhosts no >> /sysroot/etc/ssh/sshd_config
sshconf=/sysroot/etc/ssh/ssh_config
if [ -d /sysroot/etc/ssh/ssh_config.d/ ]; then
sshconf=/sysroot/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 -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
chmod 640 /sysroot/etc/ssh/*_key
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
chroot /sysroot/ update-ca-trust
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
mkdir -p /sysroot/opt/confluent/bin
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.sh > /sysroot/opt/confluent/bin/onboot.sh
chmod +x /sysroot/opt/confluent/bin/onboot.sh
cp /opt/confluent/bin/apiclient /sysroot/opt/confluent/bin
ln -s /etc/systemd/system/onboot.service /sysroot/etc/systemd/system/multi-user.target.wants/onboot.service
cp /etc/confluent/functions /sysroot/etc/confluent/functions
if grep installtodisk /proc/cmdline > /dev/null; then
. /etc/confluent/functions
run_remote installimage
exec reboot -f
fi
exec /opt/confluent/bin/start_root
@@ -0,0 +1,43 @@
#!/bin/bash
. /etc/confluent/functions
# the image will be used to deploy itself
# provide both access to image (for parsing metadata)
# and existing mounts of image (to take advantage of caching)
mount -o bind /sys /sysroot/sys
mount -o bind /dev /sysroot/dev
mount -o bind /proc /sysroot/proc
mount -o bind /run /sysroot/run
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /sysroot/run/imginst/sources/_
mount -o bind /mnt/remote /sysroot/run/imginst/sources/_
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $2}'); do
srcname=${srcmount#/dev/mapper/mproot}
srcdir=$(echo $srcmount | sed -e 's!/dev/mapper/mproot!/mnt/remote!' -e 's!_!/!g')
mkdir -p /sysroot/run/imginst/sources/$srcname
mount -o bind $srcdir /sysroot/run/imginst/sources/$srcname
done
fi
cd /sysroot/run
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
if [ ! -f /sysroot/tmp/installdisk ]; then
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
while [ ! -f /sysroot/tmp/installdisk ]; do
sleep 1
done
fi
lvm vgchange -a n
udevadm control -e
chroot /sysroot /usr/lib/systemd/systemd-udevd --daemon
chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python image2disk.py"
echo "Port 22" >> /etc/ssh/sshd_config
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
chroot /sysroot/run/imginst/targ bash -c "source /etc/confluent/functions; run_remote post.sh"
chroot /sysroot bash -c "umount \$(tac /proc/mounts|awk '{print \$2}'|grep ^/run/imginst/targ)"
@@ -0,0 +1,11 @@
[Unit]
Description=Confluent onboot hook
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/onboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,30 @@
#!/bin/sh
# This script is executed on each boot as it is
# completed. It is best to edit the middle of the file as
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
mkdir -p /var/log/confluent
exec >> /var/log/confluent/confluent-onboot.log
exec 2>> /var/log/confluent/confluent-onboot.log
tail -f /var/log/confluent/confluent-onboot.log > /dev/console &
logshowpid=$!
run_remote_python syncfileclient
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
run_remote_parts onboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
run_remote_config onboot.d
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -0,0 +1,37 @@
#!/bin/sh
# This script is executed 'chrooted' into a cloned disk target before rebooting
#
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
mkdir -p /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
tail -f /var/log/confluent/confluent-post.log > /dev/console &
logshowpid=$!
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service
mkdir -p /opt/confluent/bin
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh
chmod +x /opt/confluent/bin/firstboot.sh
systemctl enable firstboot
selinuxpolicy=$(grep ^SELINUXTYPE /etc/selinux/config |awk -F= '{print $2}')
if [ ! -z "$selinuxpolicy" ]; then
setfiles /etc/selinux/${selinuxpolicy}/contexts/files/file_contexts /etc/
fi
run_remote_python syncfileclient
run_remote post.custom
# post scripts may be placed into post.d, e.g. post.d/01-firstaction.sh, post.d/02-secondaction.sh
run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -0,0 +1,219 @@
#!/usr/bin/python
import importlib
import tempfile
import json
import os
import shutil
from importlib.machinery import SourceFileLoader
try:
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
except FileNotFoundError:
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
def partitionhostsline(line):
comment = ''
try:
cmdidx = line.index('#')
comment = line[cmdidx:]
line = line[:cmdidx].strip()
except ValueError:
pass
if not line:
return '', [], comment
ipaddr, names = line.split(maxsplit=1)
names = names.split()
return ipaddr, names, comment
class HostMerger:
def __init__(self):
self.byip = {}
self.byname = {}
self.sourcelines = []
self.targlines = []
def read_source(self, sourcefile):
with open(sourcefile, 'r') as hfile:
self.sourcelines = hfile.read().split('\n')
while not self.sourcelines[-1]:
self.sourcelines = self.sourcelines[:-1]
for x in range(len(self.sourcelines)):
line = self.sourcelines[x]
currip, names, comment = partitionhostsline(line)
if currip:
self.byip[currip] = x
for name in names:
self.byname[name] = x
def read_target(self, targetfile):
with open(targetfile, 'r') as hfile:
lines = hfile.read().split('\n')
if not lines[-1]:
lines = lines[:-1]
for y in range(len(lines)):
line = lines[y]
currip, names, comment = partitionhostsline(line)
if currip in self.byip:
x = self.byip[currip]
if self.sourcelines[x] is None:
# have already consumed this enntry
continue
self.targlines.append(self.sourcelines[x])
self.sourcelines[x] = None
continue
for name in names:
if name in self.byname:
x = self.byname[name]
if self.sourcelines[x] is None:
break
self.targlines.append(self.sourcelines[x])
self.sourcelines[x] = None
break
else:
self.targlines.append(line)
def write_out(self, targetfile):
while not self.targlines[-1]:
self.targlines = self.targlines[:-1]
while not self.sourcelines[-1]:
self.sourcelines = self.sourcelines[:-1]
with open(targetfile, 'w') as hosts:
for line in self.targlines:
hosts.write(line + '\n')
for line in self.sourcelines:
if line is not None:
hosts.write(line + '\n')
class CredMerger:
def __init__(self):
try:
with open('/etc/login.defs', 'r') as ldefs:
defs = ldefs.read().split('\n')
except FileNotFoundError:
defs = []
lkup = {}
self.discardnames = {}
self.shadowednames = {}
for line in defs:
try:
line = line[:line.index('#')]
except ValueError:
pass
keyval = line.split()
if len(keyval) < 2:
continue
lkup[keyval[0]] = keyval[1]
self.uidmin = int(lkup.get('UID_MIN', 1000))
self.uidmax = int(lkup.get('UID_MAX', 60000))
self.gidmin = int(lkup.get('GID_MIN', 1000))
self.gidmax = int(lkup.get('GID_MAX', 60000))
self.shadowlines = None
def read_passwd(self, source, targfile=False):
self.read_generic(source, self.uidmin, self.uidmax, targfile)
def read_group(self, source, targfile=False):
self.read_generic(source, self.gidmin, self.gidmax, targfile)
def read_generic(self, source, minid, maxid, targfile):
if targfile:
self.targdata = []
else:
self.sourcedata = []
with open(source, 'r') as inputfile:
for line in inputfile.read().split('\n'):
try:
name, _, uid, _ = line.split(':', 3)
uid = int(uid)
except ValueError:
continue
if targfile:
if uid <= minid or uid >= maxid:
self.targdata.append(line)
else:
self.discardnames[name] = 1
else:
if uid >= minid and uid <= maxid:
self.sourcedata.append(line)
def read_shadow(self, source):
self.shadowlines = []
try:
with open(source, 'r') as inshadow:
for line in inshadow.read().split('\n'):
try:
name, _ = line.split(':' , 1)
except ValueError:
continue
if name in self.discardnames:
continue
self.shadowednames[name] = 1
self.shadowlines.append(line)
except FileNotFoundError:
return
def write_out(self, outfile):
with open(outfile, 'w') as targ:
for line in self.targdata:
targ.write(line + '\n')
for line in self.sourcedata:
targ.write(line + '\n')
if outfile == '/etc/passwd':
if self.shadowlines is None:
self.read_shadow('/etc/shadow')
with open('/etc/shadow', 'w') as shadout:
for line in self.shadowlines:
shadout.write(line + '\n')
for line in self.sourcedata:
name, _ = line.split(':', 1)
if name in self.shadowednames:
continue
shadout.write(name + ':!:::::::\n')
if outfile == '/etc/group':
if self.shadowlines is None:
self.read_shadow('/etc/gshadow')
with open('/etc/gshadow', 'w') as shadout:
for line in self.shadowlines:
shadout.write(line + '\n')
for line in self.sourcedata:
name, _ = line.split(':' , 1)
if name in self.shadowednames:
continue
shadout.write(name + ':!::\n')
def synchronize():
tmpdir = tempfile.mkdtemp()
try:
ac = apiclient.HTTPSClient()
data = json.dumps({'merge': tmpdir})
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
if status == 202:
while status != 204:
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
if not isinstance(rsp, str):
rsp = rsp.decode('utf8')
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
if os.path.exists(pendpasswd):
cm = CredMerger()
cm.read_passwd(pendpasswd, targfile=False)
cm.read_passwd('/etc/passwd', targfile=True)
cm.write_out('/etc/passwd')
pendgroup = os.path.join(tmpdir, 'etc/group')
if os.path.exists(pendgroup):
cm = CredMerger()
cm.read_group(pendgroup, targfile=False)
cm.read_group('/etc/group', targfile=True)
cm.write_out('/etc/group')
pendhosts = os.path.join(tmpdir, 'etc/hosts')
if os.path.exists(pendhosts):
cm = HostMerger()
cm.read_source(pendhosts)
cm.read_target('/etc/hosts')
cm.write_out('/etc/hosts')
finally:
shutil.rmtree(tmpdir)
if __name__ == '__main__':
synchronize()
@@ -0,0 +1,13 @@
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
# /etc/hosts
# Entries under MERGE will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
MERGE:
# /etc/passwd
# /etc/group
@@ -35,11 +35,13 @@ reboot
%packages
@^minimal-environment
#-kernel-uek # This can opt out of the UEK for the relevant distribution
bind-utils
chrony
rsync
python3
tar
pciutils
python3
rsync
tar
-iwl*-firmware
%include /tmp/addonpackages
%include /tmp/cryptpkglist
%end
@@ -15,6 +15,9 @@ exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
logshowpid=$!
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
@@ -1,4 +1,7 @@
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
@@ -51,7 +54,7 @@ source_remote() {
chmod +x $1
cmd=$1
shift
source $cmd
source ./$cmd
cd - > /dev/null
return $retcode
}
@@ -46,6 +46,11 @@ class DiskInfo(object):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
@property
def priority(self):
@@ -14,6 +14,6 @@ if lspci -d 15b3:: -n |grep 15b3 > /dev/null; then
tar xf infiniband/mofed.tgz
# The rest is common between tar and iso
cd MLNX_OFED*
./mlnxofedinstall --force
./mlnxofedinstall --force --without-32bit
fi
@@ -1,10 +1,11 @@
# Add needed base packages to the install
cat << EOF >> /tmp/addonpackages
gcc-gfortran
kernel-modules-extra
lsof
perl
pkgconf-pkg-config
tcsh
lsof
tk
gcc-gfortran
tcl
tcsh
tk
EOF
@@ -76,6 +76,14 @@ fi
export confluent_mgr confluent_profile nodename
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
if [ -e /usr/libexec/platform-python ]; then
python=/usr/libexec/platform-python
elif [ -e /usr/bin/python3 ]; then
python=/usr/byn/python3
else
python=/usr/bin/python
fi
$python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
run_remote pre.custom
run_remote_parts pre.d
if [ ! -e /tmp/installdisk ]; then
@@ -86,12 +94,4 @@ if [ -e /tmp/installdisk -a ! -e /tmp/partitioning ]; then
echo ignoredisk --only-use $(cat /tmp/installdisk) >> /tmp/partitioning
echo autopart --nohome $LUKSPARTY >> /tmp/partitioning
fi
if [ -e /usr/libexec/platform-python ]; then
python=/usr/libexec/platform-python
elif [ -e /usr/bin/python3 ]; then
python=/usr/byn/python3
else
python=/usr/bin/python
fi
$python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
kill $logshowpid
@@ -12,6 +12,8 @@ export confluent_mgr confluent_profile nodename
cp -a /etc/confluent /mnt/sysimage/etc
chmod -R og-rwx /mnt/sysimage/etc/confluent
cp /tmp/functions /mnt/sysimage/etc/confluent/
hostnamectl set-hostname $nodename
cp /etc/hostname /mnt/sysimage/etc/hostname
. /tmp/functions
if [ -f /tmp/cryptboot ]; then
cp /tmp/cryptboot /mnt/sysimage/tmp/
@@ -134,7 +134,9 @@ class CredMerger:
else:
self.discardnames[name] = 1
else:
if uid >= minid and uid <= maxid:
if name[0] in ('+', '#', '@'):
self.sourcedata.append(line)
elif uid >= minid and uid <= maxid:
self.sourcedata.append(line)
def read_shadow(self, source):
@@ -167,6 +169,8 @@ class CredMerger:
shadout.write(line + '\n')
for line in self.sourcedata:
name, _ = line.split(':', 1)
if name[0] in ('+', '#', '@'):
continue
if name in self.shadowednames:
continue
shadout.write(name + ':!:::::::\n')
@@ -216,4 +220,4 @@ def synchronize():
if __name__ == '__main__':
synchronize()
synchronize()
@@ -8,13 +8,13 @@ if ! grep console= /proc/cmdline >& /dev/null; then
echo $autocons > /tmp/01-autocons.devnode
if [ ! -z "$autocons" ]; then
echo "Using $(cat /tmp/01-autocons.conf)"
(while :; do tmux a <> $autocons >&0 2>&1; done) &
(while :; do TERM=xterm-256color tmux a <> $autocons >&0 2>&1; done) &
(while :; do TERM=linux tmux a <> /dev/tty1 >&0 2>&1; done) &
else
(while :; do tmux a <> /dev/console >&0 2>&1; done) &
(while :; do TERM=linux tmux a <> /dev/console >&0 2>&1; done) &
fi
else
(while :; do tmux a <> /dev/console >&0 2>&1; done) &
(while :; do TERM=xterm-256color tmux a <> /dev/console >&0 2>&1; done) &
(while :; do TERM=linux tmux a <> /dev/tty1 >&0 2>&1; done) &
fi
(while :; do TERM=linux tmux <> /dev/tty2 >&0 2>&1; done) &
@@ -55,17 +55,74 @@ done
cd /
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
hostname $nodename
tpmdir=$(mktemp -d)
cd $tpmdir
lasthdl=""
oldumask=$(umask)
umask 0077
for hdl in $(tpm2_getcap handles-persistent|awk '{print $2}'); do
tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
unsealeddata=$(tpm2_unseal --auth=session:session.ctx -Q -c $hdl 2>/dev/null)
tpm2_flushcontext session.ctx
if [[ $unsealeddata == "CONFLUENT_APIKEY:"* ]]; then
confluent_apikey=${unsealeddata#CONFLUENT_APIKEY:}
echo $confluent_apikey > /etc/confluent/confluent.apikey
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
lasthdl=$hdl
fi
done
cd - > /dev/null
rm -rf $tpmdir
/usr/libexec/platform-python /opt/confluent/bin/apiclient /confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg
umask $oldumask
new_apikey=$(cat /etc/confluent/confluent.apikey)
if [ "$new_apikey" != "$confluent_apikey" ]; then
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
tmpdir=$(mktemp -d)
cd $tmpdir
tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
tpm2_createprimary -G ecc -Q --key-context=prim.ctx
(echo -n "CONFLUENT_APIKEY:$new_apikey") | tpm2_create -Q --policy=pcr15.sha256.policy --public=data.pub --private=data.priv -i - -C prim.ctx
tpm2_load -Q --parent-context=prim.ctx --public=data.pub --private=data.priv --name=confluent.apikey --key-context=data.ctx
tpm2_evictcontrol -Q -c data.ctx
tpm2_flushcontext session.ctx
cd - > /dev/null
rm -rf $tmpdir
fi
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2 2> /dev/null
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
autoconfigmethod=$(grep ipv6_method /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "static" ]; then
ipaddr=$(grep ^ipv6_address: /etc/confluent/confluent.deploycfg)
ipaddr=${ipaddr#ipv6_address: }
ipgw=$(grep ^ipv6_gateway: /etc/confluent/confluent.deploycfg)
ipgw=${ipgw#ipv6_gateway: }
if [ "$ipgw" = "null" ]; then
ipgw=""
fi
ipnm=$(grep ^prefix: /etc/confluent/confluent.deploycfg)
ipnm=${ipnm#prefix: }
echo "Setting up $ifname as static at $ipaddr/$ipnm"
ip addr add dev $ifname $ipaddr/$ipnm
if [ ! -z "$ipgw" ]; then
ip route add default via $ipgw
fi
fi
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "dhcp" ]; then
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
echo "Complete:"
ip addr show dev $ifname
else
elif [ "$autocinfigmethtod" = "static" ]; then
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
@@ -1,4 +1,7 @@
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
@@ -12,6 +12,9 @@ profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
export nodename mgr profile
. /etc/confluent/functions
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
run_remote firstboot.custom
@@ -0,0 +1,193 @@
get_remote_apikey() {
while [ -z "$confluent_apikey" ]; do
/opt/confluent/bin/clortho $nodename $confluent_mgr > /etc/confluent/confluent.apikey
if grep ^SEALED: /etc/confluent/confluent.apikey > /dev/null; then
# we don't support remote sealed api keys
echo > /etc/confluent/confluent.apikey
fi
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
if [ -z "$confluent_apikey" ]; then
echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..."
sleep 10
else
tmpdir=$(mktemp -d)
cd $tmpdir
tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
tpm2_createprimary -G ecc -Q --key-context=prim.ctx
(echo -n "CONFLUENT_APIKEY:";cat /etc/confluent/confluent.apikey) | tpm2_create -Q --policy=pcr15.sha256.policy --public=data.pub --private=data.priv -i - -C prim.ctx
tpm2_load -Q --parent-context=prim.ctx --public=data.pub --private=data.priv --name=confluent.apikey --key-context=data.ctx
tpm2_evictcontrol -Q -c data.ctx
tpm2_flushcontext session.ctx
cd - > /dev/null
rm -rf $tmpdir
fi
done
}
root=1
rootok=1
netroot=confluent
clear
mkdir -p /etc/ssh
mkdir -p /var/tmp/
mkdir -p /var/lib/empty
mkdir -p /var/empty/sshd
mkdir -p /etc/confluent
sed -i '/^root:x/d' /etc/passwd
echo root:x:0:0::/:/bin/bash >> /etc/passwd
echo sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin >> /etc/passwd
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
autocons=${autocons##*/}
echo "Automatic console configured for $autocons"
fi
echo "Initializing confluent diskless environment"
echo -n "udevd: "
/usr/lib/systemd/systemd-udevd --daemon
echo -n "Loading drivers..."
udevadm trigger
udevadm trigger --type=devices --action=add
udevadm settle
modprobe ib_ipoib
modprobe ib_umad
modprobe hfi1
modprobe mlx5_ib
echo "done"
cat > /etc/ssh/sshd_config << EOF
Port 2222
Subsystem sftp /usr/libexec/openssh/sftp-server
PermitRootLogin yes
AuthorizedKeysFile .ssh/authorized_keys
EOF
mkdir /root/.ssh
mkdir /.ssh
cat /ssh/*pubkey > /root/.ssh/authorized_keys 2>/dev/null
cp /root/.ssh/authorized_keys /.ssh/
cat /tls/*.pem > /etc/confluent/ca.pem
mkdir -p /var/lib/ca-certificates/
cat /tls/*.pem > /var/lib/ca-certificates/ca-bundle.pem
TRIES=0
oldumask=$(umask)
umask 0077
tpmdir=$(mktemp -d)
cd $tpmdir
lasthdl=""
for hdl in $(tpm2_getcap handles-persistent|awk '{print $2}'); do
tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
unsealeddata=$(tpm2_unseal --auth=session:session.ctx -Q -c $hdl 2>/dev/null)
tpm2_flushcontext session.ctx
if [[ $unsealeddata == "CONFLUENT_APIKEY:"* ]]; then
confluent_apikey=${unsealeddata#CONFLUENT_APIKEY:}
echo $confluent_apikey > /etc/confluent/confluent.apikey
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
lasthdl=$hdl
fi
done
cd - > /dev/null
rm -rf $tpmdir
touch /etc/confluent/confluent.info
cd /sys/class/net
echo -n "Scanning for network configuration..."
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 30 ]; do
TRIES=$((TRIES + 1))
for i in *; do
ip link set $i up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
done
cd /
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
hostname $nodename
confluent_mgr=$(grep '^EXTMGRINFO:.*1$' /etc/confluent/confluent.info | head -n 1|awk -F': ' '{print $2}' | awk -F'|' '{print $1}')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info|head -n 1 | awk '{print $2}')
fi
if [[ $confluent_mgr == *%* ]]; then
echo $confluent_mgr | awk -F% '{print $2}' > /tmp/confluent.ifidx
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
fi
ready=0
while [ $ready = "0" ]; do
get_remote_apikey
if [[ $confluent_mgr == *:* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
tmperr=$(mktemp)
curl -sSf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg 2> $tmperr
if grep 401 $tmperr > /dev/null; then
confluent_apikey=""
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
else
ready=1
fi
rm $tmperr
done
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2
umask $oldumask
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "dhcp" ]; then
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
echo "Complete:"
ip addr show dev $ifname
else
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^prefix: /etc/confluent/confluent.deploycfg)
v4nm=${v4nm#prefix: }
echo "Setting up $ifname as static at $v4addr/$v4nm"
ip addr add dev $ifname $v4addr/$v4nm
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
mkdir -p /run/confluent
echo -e "BOOTPROTO='static'\nSTARTMODE='auto'" >> /run/confluent/ifcfg-$ifname
echo "IPADDR='$v4addr/$v4nm'" >> /run/confluent/ifcfg-$ifname
if [ ! -z "$v4gw" ]; then
echo defafult $v4gw - $ifname > /run/confluent/ifroute-$ifname
fi
fi
echo -n "Initializing ssh..."
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /etc/ssh/sshd_config
fi
echo HostKey $privfile >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_urls=""
for addr in $(grep ^MANAGER: /etc/confluent/confluent.info|awk '{print $2}'|sed -e s/%/%25/); do
if [[ $addr == *:* ]]; then
confluent_urls="$confluent_urls $confluent_proto://[$addr]/confluent-public/os/$confluent_profile/rootimg.sfs"
else
confluent_urls="$confluent_urls $confluent_proto://$addr/confluent-public/os/$confluent_profile/rootimg.sfs"
fi
done
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg| awk '{print $2}')
mkdir -p /etc/confluent
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
. /etc/confluent/functions
source_remote imageboot.sh
@@ -0,0 +1,144 @@
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
curlargs=""
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
set_confluent_vars
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(/usr/libexec/platform-python $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(/usr/libexec/platform-python $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source ./$cmd
cd - > /dev/null
return $retcode
}
run_remote() {
requestedcmd="'$*'"
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d)
fi
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
chmod +x $1
cmd=$1
if [ -x /usr/bin/chcon ]; then
chcon system_u:object_r:bin_t:s0 $cmd
fi
shift
./$cmd $*
retcode=$?
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
return $retcode
}
run_remote_python() {
echo
set_confluent_vars
if [ -f /etc/confluent/ca.pem ]; then
curlargs=" --cacert /etc/confluent/ca.pem"
fi
echo '---------------------------------------------------------------------------'
echo Running python script "'$*'" from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
tmpdir=$(mktemp -d)
echo Executing in $tmpdir
cd $tmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
else
/usr/bin/python $*
fi
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
return $retcode
}
run_remote_config() {
echo
set_confluent_vars
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
echo '---------------------------------------------------------------------------'
echo Requesting to run remote configuration for "'$*'" from $confluent_mgr under profile $confluent_profile
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
else
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
/usr/bin/python $apiclient /confluent-api/self/remoteconfig/status -w 204
fi
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
@@ -0,0 +1,139 @@
. /lib/dracut-lib.sh
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs"
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
fi
/opt/confluent/bin/confluent_imginfo /mnt/remoteimg/rootimg.sfs > /tmp/rootimg.info
loopdev=$(losetup -f)
export mountsrc=$loopdev
losetup -r $loopdev /mnt/remoteimg/rootimg.sfs
if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/profileprivate/pending/rootimg.key > /tmp/rootimg.key
cipher=$(head -n 1 /tmp/rootimg.key)
key=$(tail -n 1 /tmp/rootimg.key)
len=$(wc -c /mnt/remoteimg/rootimg.sfs | awk '{print $1}')
len=$(((len-4096)/512))
dmsetup create cryptimg --table "0 $len crypt $cipher $key 0 $loopdev 8"
/opt/confluent/bin/confluent_imginfo /dev/mapper/cryptimg > /tmp/rootimg.info
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
. /tmp/mountparts.sh
fi
#mount -t tmpfs overlay /mnt/overlay
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
fi
mkdir -p /sysroot/etc/ssh
mkdir -p /sysroot/etc/confluent
mkdir -p /sysroot/root/.ssh
cp /root/.ssh/* /sysroot/root/.ssh
chmod 700 /sysroot/root/.ssh
cp /etc/confluent/* /sysroot/etc/confluent/
cp /etc/ssh/*key* /sysroot/etc/ssh/
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /sysroot/etc/ssh/sshd_config
fi
echo HostKey $privfile >> /sysroot/etc/ssh/sshd_config
done
mkdir -p /sysroot/dev /sysroot/sys /sysroot/proc /sysroot/run
if [ ! -z "$autocons" ]; then
autocons=${autocons%,*}
mkdir -p /run/systemd/generator/getty.target.wants
ln -s /usr/lib/systemd/system/serial-getty@.service /run/systemd/generator/getty.target.wants/serial-getty@${autocons}.service
fi
while [ ! -e /sysroot/sbin/init ]; do
echo "Failed to access root filesystem or it is missing /sbin/init"
echo "System should be accessible through ssh at port 2222 with the appropriate key"
while [ ! -e /sysroot/sbin/init ]; do
sleep 1
done
done
rootpassword=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg)
rootpassword=${rootpassword#rootpassword: }
if [ "$rootpassword" = "null" ]; then
rootpassword=""
fi
if [ ! -z "$rootpassword" ]; then
sed -i "s@root:[^:]*:@root:$rootpassword:@" /sysroot/etc/shadow
fi
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
echo HostbasedAuthentication yes >> /sysroot/etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /sysroot/etc/ssh/sshd_config
echo IgnoreRhosts no >> /sysroot/etc/ssh/sshd_config
sshconf=/sysroot/etc/ssh/ssh_config
if [ -d /sysroot/etc/ssh/ssh_config.d/ ]; then
sshconf=/sysroot/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 -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
chmod 600 /sysroot/etc/ssh/*_key
chroot /sysroot cat /etc/confluent/ca.pem >> /sysroot/var/lib/ca-certificates/ca-bundle.pem
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
mkdir -p /sysroot/opt/confluent/bin
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.sh > /sysroot/opt/confluent/bin/onboot.sh
chmod +x /sysroot/opt/confluent/bin/onboot.sh
cp /opt/confluent/bin/apiclient /sysroot/opt/confluent/bin
ln -s /etc/systemd/system/onboot.service /sysroot/etc/systemd/system/multi-user.target.wants/onboot.service
cp /etc/confluent/functions /sysroot/etc/confluent/functions
nameserversec=0
nameservers=""
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]]; then
nameservers="$nameservers"${entry#- }" "
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /etc/confluent/confluent.deploycfg
nameservers=${nameservers% }
sed -i 's/^NETCONFIG_DNS_STATIC_SERVERS="/NETCONFIG_DNS_STATIC_SERVERS="'"$nameservers"/ /sysroot/etc/sysconfig/network/config
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
sed -i 's/^NETCONFIG_DNS_STATIC_SEARCHLIST="/NETCONFIG_DNS_STATIC_SEARCHLIST="'$dnsdomain/ /sysroot/etc/sysconfig/network/config
cp /run/confluent/ifroute-* /run/confluent/ifcfg-* /sysroot/etc/sysconfig/network
if grep installtodisk /proc/cmdline > /dev/null; then
. /etc/confluent/functions
run_remote installimage
exec reboot -f
fi
exec /opt/confluent/bin/start_root
@@ -0,0 +1,11 @@
[Unit]
Description=Confluent onboot hook
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/onboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,28 @@
#!/bin/sh
# This script is executed on each boot as it is
# completed. It is best to edit the middle of the file as
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
mkdir -p /var/log/confluent
exec >> /var/log/confluent/confluent-onboot.log
exec 2>> /var/log/confluent/confluent-onboot.log
tail -f /var/log/confluent/confluent-onboot.log > /dev/console &
logshowpid=$!
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
run_remote_parts onboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
run_remote_config onboot.d
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -66,7 +66,7 @@ if [ "$textconsole" = "true" ] && ! grep console= /proc/cmdline > /dev/null && [
echo "Serial console has been autodected and enabled read-only for install" > ${autocons%,*}
echo "It will be read-write after install" > ${autocons%,*}
echo "If a fully functional console is desired over serial, add console=${autocons#/dev/} " > ${autocons%,*}
echo "to kerneralgs in the profile.yaml file of the profile and run 'osdeploy updateboot <profile>" > ${autocons%,*}
echo "to kernelargs in the profile.yaml file of the profile and run 'osdeploy updateboot <profile>" > ${autocons%,*}
#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%,*}
+19 -21
View File
@@ -1,34 +1,34 @@
<?xml version="1.0"?>
<!DOCTYPE profile SYSTEM "/usr/share/YaST2/include/autoinstall/profile.dtd">
<!DOCTYPE profile>
<!--
This autoyast file will be processed by pre.sh before applying. See pre.sh for
info on modifying its behavior, and also search and replace '%%' to remove
dynamic behavior and replace with static configuration.
-->
<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">
<profile xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns">
<timezone>
<hwclock>UTC</hwclock>
<!--INSERT:/tmp/timezone-->
</timezone>
<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>
<add_on_products config:type="list">
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>sle-module-basesystem</product><product_dir>/Module-Basesystem</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>sle-module-hpc</product><product_dir>/Module-HPC</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>sle-module-server-applications</product><product_dir>/Module-Server-Applications</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>sle-module-containers</product><product_dir>/Module-Containers</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>SLE_HPC</product><product_dir>/Product-HPC</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>Legacy-Module</product><product_dir>/Module-Legacy</product_dir></listentry>
</add_on_products>
</add-on>
%%ENDIFSLE%%
<xi:include href="file:///tmp/bootloader.xml"/>
<!--INSERT:/tmp/bootloader.xml-->
<software>
%%IFSLE%%
<products config:type="list">
@@ -39,6 +39,8 @@ dynamic behavior and replace with static configuration.
<pattern>base</pattern>
</patterns>
<packages config:type="list">
<package>openssh</package>
<package>iputils</package>
<package>python3</package>
<package>openssl</package>
<package>chrony</package>
@@ -79,7 +81,7 @@ dynamic behavior and replace with static configuration.
<username>root</username>
<user_password>%%ROOTPASSWORD%%</user_password>
<encrypted config:type="boolean">true</encrypted>
<xi:include href="file:///tmp/rootkeys.xml"/>
<!--INSERT:/tmp/rootkeys.xml-->
</user>
</users>
<networking>
@@ -99,7 +101,6 @@ dynamic behavior and replace with static configuration.
<pre-scripts config:type="list">
<script>
<filename>preinstall.sh</filename>
<interpreter>shell</interpreter>
<source>
<![CDATA[
#!/bin/sh
@@ -115,7 +116,6 @@ curl $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.
<chroot-scripts config:type="list">
<script>
<filename>chroot.sh</filename>
<interpreter>shell</interpreter>
<source>
<![CDATA[
#!/bin/sh
@@ -135,14 +135,12 @@ cp /mnt/etc/confluent/post.sh /mnt/var/adm/autoinstall/scripts/
<script>
<chrooted config:type="boolean">true</chrooted>
<filename>post.sh</filename>
<interpreter>shell</interpreter>
<location>file:///etc/confluent/post.sh</location>
</script>
</chroot-scripts>
<init-scripts config:type="list">
<script>
<filename>firstboot.sh</filename>
<interpreter>shell</interpreter>
<location>file:///etc/confluent/firstboot.sh</location>
</script>
</init-scripts>
@@ -2,19 +2,22 @@
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
profile=$(basename $2)
if [[ $profile =~ ^sle.* ]]; then
if ls $1/Product-* >& /dev/null; then
ln -s $1 $2/product
else
ln -s ${1%1}2 $2/product
fi
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
sed -i '/%%IFSLE%%/,/%%ENDIFSLE%%/d' $2/autoyast
fi
@@ -10,6 +10,9 @@ confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
. /etc/confluent/functions
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
for i in /etc/ssh/ssh_host*key.pub; do
certname=${i/.pub/-cert.pub}
@@ -1,4 +1,7 @@
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
@@ -38,4 +38,10 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
ntpcfg=$(paste -sd '' /tmp/ntp.cfg)
fi
run_remote_python getinstalldisk
sed -e s!%%INSTDISK%%!/dev/$(cat /tmp/installdisk)! -e s!%%NODENAME%%!$nodename! -e "s!<networking>!$ntpcfg<networking>!" -e "s?%%ROOTPASSWORD%%?${rootpw}?" /tmp/profile/autoinst.xml > /tmp/profile/modified.xml
sed -e s'!'%%INSTDISK%%'!'/dev/$(cat /tmp/installdisk)'!' -e s'!'%%NODENAME%%'!'$nodename'!' -e 's!<networking\(.*\)>!'"$ntpcfg"'<networking\1>!' -e "s?%%ROOTPASSWORD%%?${rootpw}?" /tmp/profile/autoinst.xml > /tmp/profile/modified.xml
if grep append /tmp/bootloader.xml > /dev/null; then
sed -i 's@</general>@</general>'"$(tr -d '\n' < /tmp/bootloader.xml)"'@' /tmp/profile/modified.xml
fi
sed -i 's#root</username>#root</username>'"$(tr -d '\n' < /tmp/rootkeys.xml)"'#' /tmp/profile/modified.xml
sed -i 's@/hwclock>@/hwclock>'"$(tr -d '\n' < /tmp/timezone)"'@' /tmp/profile/modified.xml
sed -i 's@<media_url/>@'"$(tr -d '\n' < /tmp/pkgurl)"'@' /tmp/profile/modified.xml
@@ -9,6 +9,9 @@ fi
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}')
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
hostnamectl set-hostname $(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
touch /etc/cloud/cloud-init.disabled
source /etc/confluent/functions
@@ -1,4 +1,7 @@
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
+6 -2
View File
@@ -1,8 +1,12 @@
CC := gcc
CFLAGS := -Os
TARGETS := copernicus autocons
TARGETS := copernicus autocons start_root confluent_imginfo
all: $(TARGETS) clortho
all: $(TARGETS) clortho urlmount
urlmount: urlmount.c
$(CC) $(CFLAGS) -o $@ $^ -D_FILE_OFFSET_BITS=64 -lcurl -lm -lfuse -lpthread
strip -s $@
clortho: clortho.c
$(CC) $(CFLAGS) -o $@ $^ -lcrypt
+1 -1
View File
@@ -95,7 +95,7 @@ int main(int argc, char* argv[]) {
timeout.tv_usec = 500000;
FD_ZERO(&set);
FD_SET(ttyf, &set);
write(ttyf, "\0337\033[999;999H\033[6n\0338", 18);
if (write(ttyf, "\0337\033[999;999H\033[6n\0338", 18) < 0) {};
while (select(ttyf + 1, &set, NULL, NULL, &timeout) > 0) {
if ((tmpi = read(ttyf, buff + bufflen, 127 - bufflen)) < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
+2 -2
View File
@@ -116,7 +116,7 @@ int main(int argc, char* argv[]) {
currtype = buffer[0];
if (currtype & 0b10000000) {
currlen = buffer[1] << 8;
read(sock, buffer, 1);
while (read(sock, buffer, 1) < 0) {};
currlen |= buffer[0];
} else {
currlen = buffer[1];
@@ -131,7 +131,7 @@ int main(int argc, char* argv[]) {
buffer[currlen] = 0;
}
if (currtype == 2) {
dprintf(sock, "\x03%c", currlen);
dprintf(sock, "\x03%c", (int)currlen);
ret = write(sock, buffer, currlen);
slen = strlen(cryptedpass) & 0xff;
dprintf(sock, "\x04%c%s", slen, cryptedpass);
@@ -0,0 +1,82 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int read_part(FILE* img, long int imgsize) {
char mountpath[65537];
char devpath[65537];
char fstype[65537];
uint16_t shortlength;
uint32_t length;
uint64_t longlength;
fread(&shortlength, 2, 1, img);
shortlength = be16toh(shortlength);
fread(mountpath, 1, shortlength, img);
mountpath[shortlength] = 0;
fread(&length, 4, 1, img);
length = be32toh(length);
fseek(img, length, SEEK_CUR); // skip json section that we don't support
fread(&longlength, 8, 1, img); // minimum size in bytes
longlength = be64toh(longlength);
printf("%ld\t", longlength);
fread(&longlength, 8, 1, img); // default size in bytes
longlength = be64toh(longlength);
printf("%ld\t", longlength);
fread(&shortlength, 2, 1, img); // length of filesystem type
shortlength = be16toh(shortlength);
fread(fstype, 1, shortlength, img);
fstype[shortlength] = 0;
fread(&shortlength, 2, 1, img); // length of DEVICE
shortlength = be16toh(shortlength);
fread(devpath, 1, shortlength, img);
devpath[shortlength] = 0;
fread(&shortlength, 2, 1, img);
shortlength = be16toh(shortlength);
fseek(img, shortlength, SEEK_CUR); // Skip the padding
fread(&longlength, 8, 1, img);
longlength = be64toh(longlength);
printf("%ld\t", ftell(img) / 512);
printf("%ld\t", longlength / 512);
printf("%s\t%s\t%s\n", fstype, devpath, mountpath);
fseek(img, longlength, SEEK_CUR);
return (ftell(img) < imgsize);
}
int main(int argc, char* argv[]) {
FILE* img;
long int imgsize;
char buffer[20];
img = fopen(argv[1], "rb");
fseek(img, 0, SEEK_END);
imgsize = ftell(img);
fseek(img, 0, SEEK_SET);
if (fread(buffer, 1, 16, img) < 16) {
fprintf(stderr, "Unable to read header of image\n");
exit(1);
}
if ((memcmp(buffer, "hsqs", 4) == 0) || (memcmp(buffer, "sqsh", 4) == 0)) {
printf("Format: squashfs\n");
exit(0);
}
if (memcmp(buffer, "\x63\x7b\x9d\x26\xb7\xfd\x48\x30\x89\xf9\x11\xcf\x18\xfd\xff\xa1", 16) == 0) {
printf("Format: confluent_multisquash\nminsize\tdefsize\toffset\tsize\tfstype\torigdev\tmount\n");
fread(buffer, 1, 1, img);
fseek(img, buffer[0], SEEK_CUR);
while (read_part(img, imgsize));
exit(0);
}
if (memcmp(buffer, "\xaa\xd5\x0f\x7e\x5d\xfb\x4b\x7c\xa1\x2a\xf4\x0b\x6d\x94\xf7\xfc", 16) == 0) {
fread(buffer, 1, 1, img);
fseek(img, buffer[0], SEEK_CUR);
fread(buffer, 1, 1, img);
if (buffer[0] == 0) {
printf("Format: confluent_crypted\n");
exit(0);
}
}
fprintf(stderr, "Unrecognized image format\n");
exit(1);
}
+17
View File
@@ -0,0 +1,17 @@
#include <unistd.h>
#include <stdio.h>
#include <sys/mount.h>
#define __USE_GNU
#include <sched.h>
int main(int argc, char* argv[]) {
unshare(CLONE_NEWNS);
mount("/dev", "/sysroot/dev", NULL, MS_MOVE, NULL);
mount("/proc", "/sysroot/proc", NULL, MS_MOVE, NULL);
mount("/sys", "/sysroot/sys", NULL, MS_MOVE, NULL);
mount("/run", "/sysroot/run", NULL, MS_MOVE, NULL);
if (chdir("/sysroot") < 0) { fprintf(stderr, "Unable to chdir!\n"); }
mount("/sysroot", "/", NULL, MS_MOVE, NULL);
if (chroot(".") < 0) { fprintf(stderr, "Failed to chroot!\n"); }
if (chdir("/") < 0) { fprintf(stderr, "Unable to chdir after chroot!\n"); }
execl("/sbin/init", "/sbin/init", NULL);
}
+283
View File
@@ -0,0 +1,283 @@
/*
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2021 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.
*/
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <curl/curl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <pthread.h>
#include <unistd.h>
CURL *curl;
char curlerror[CURL_ERROR_SIZE];
curl_off_t filesize;
typedef struct downloadbuffer {
char *response;
size_t completed;
size_t total;
} downloadbuffer;
#define MAX_FILE_LEN 1024
#define MAX_URL_PATHS 512
static char filename[MAX_FILE_LEN];
static int urlidx, newidx;
static char* urls[MAX_URL_PATHS];
void *http_rechecker(void *argp) {
CURL *checkurl;
int tmpidx, tmpval;
tmpidx = open("/dev/urandom", O_RDONLY);
if (tmpidx <= 0 || read(tmpidx, (char*)&tmpval, 4) < 0)
tmpval = time(NULL);
if (tmpidx > 0)
close(tmpidx);
srand(tmpval);
checkurl = curl_easy_init();
curl_easy_setopt(checkurl, CURLOPT_ERRORBUFFER, curlerror);
//We want to consider error conditions fatal, rather than
//passing error text as data
curl_easy_setopt(checkurl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(checkurl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(checkurl, CURLOPT_NOBODY, 1);
while (1) {
sleep(25 + rand() % 10); // Spread out retries across systems
tmpidx = 0;
while (tmpidx < urlidx && tmpidx < newidx && urls[tmpidx] != NULL) {
curl_easy_setopt(checkurl, CURLOPT_URL, urls[tmpidx]);
if (curl_easy_perform(checkurl) == CURLE_OK)
newidx = tmpidx;
else
tmpidx++;
}
}
}
static int http_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
if (strcmp(path, "/") != 0) // We don't support subdirs
return -ENOENT;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, filename + 1, NULL, 0);
return 0;
}
size_t fill_buffer(char *data, size_t size, size_t nmemb, downloadbuffer *userdata) {
size_t amount;
amount = size * nmemb;
if (userdata->total < amount + userdata->completed) return 0;
memcpy(&(userdata->response[userdata->completed]), data, amount);
userdata->completed += amount;
return amount;
}
static int http_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi) {
char headbuffer[512];
double dldbl = 0.0;
int startidx;
int reconnecting = 0;
FILE* fd;
startidx = urlidx;
memset(buf, 0, size);
curl_off_t downloaded;
//Would be needed for multithread, however preferring to conserve
//filehandles rather than go multithread
// Some comparisons showed that the threaded performance boost doesn't
// do even offset the overhead of the new curl handles, so better
// to use single threaded curl overall for now
//CURL *tmpcurl = curl_easy_duphandle(curl);
downloadbuffer dlbuf;
dlbuf.response = buf;
dlbuf.completed = 0;
dlbuf.total = size;
fd = NULL;
if (strcmp(path, filename) != 0) return -ENOENT;
memset(headbuffer, 0, 512);
if (offset >= filesize) return 0;
if (offset + size - 1 >= filesize) size = filesize - offset - 1;
snprintf(headbuffer, 512, "%ld-%ld", offset, offset + size - 1);
curl_easy_setopt(curl, CURLOPT_RANGE, headbuffer);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &dlbuf);
if (newidx < MAX_URL_PATHS) {
reconnecting = 1;
urlidx = newidx;
newidx = MAX_URL_PATHS;
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<5>urlmount: Connecting to %s\n", urls[urlidx]);
fclose(fd);
curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]);
}
while (curl_easy_perform(curl) != CURLE_OK) {
reconnecting = 1;
fd = fopen("/dev/kmsg", "w+");
dlbuf.completed = 0;
fprintf(fd, "<4>urlmount: error while communicating with %s: %s\n", urls[urlidx], curlerror);
fclose(fd);
urlidx++;
if (urls[urlidx] == NULL)
urlidx = 0;
if (urlidx == startidx) {
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<1>urlmount: All connections to source are down\n");
fclose(fd);
sleep(10);
}
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<5>urlmount: Connecting to %s\n", urls[urlidx]);
fclose(fd);
curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]);
}
if (reconnecting) {
fd = fopen("/dev/kmsg", "w+");
fprintf(fd, "<5>urlmount: Successfully connected to %s\n", urls[urlidx]);
fclose(fd);
}
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &dldbl);
downloaded = round(dldbl);
//Would be needed for multithread
//curl_easy_cleanup(tmpcurl);
return downloaded;
}
static int http_open(const char *path, struct fuse_file_info *fi) {
if (strcmp(path, filename) != 0)
return -ENOENT;
if ((fi->flags & 3) != O_RDONLY)
return -EACCES;
return 0;
}
static void* http_init(struct fuse_conn_info *conn) {
// Because we fork, we need to redo curl
// or else suffer the wrath of NSS TLS
pthread_t tid;
curl_global_init(CURL_GLOBAL_DEFAULT);
pthread_create(&tid, NULL, http_rechecker, NULL);
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerror);
//We want to consider error conditions fatal, rather than
//passing error text as data
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fill_buffer);
return NULL;
}
static int http_getattr(const char *path, struct stat *st) {
memset(st, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
st->st_mode = S_IFDIR | 0555;
st->st_nlink = 2;
} else if (strcmp(path, filename) == 0) {
st->st_mode = S_IFREG | 0444;
st->st_nlink = 1;
st->st_size = filesize; // TODO: fix with curl HEAD
} else
return -ENOENT;
return 0;
}
static const struct fuse_operations http_ops = {
.getattr = http_getattr,
.readdir = http_readdir,
.read = http_read,
.open = http_open,
.init = http_init,
};
int main(int argc, char* argv[]) {
char *tmp;
double fsize;
unsigned int i;
int j;
j = 0;
memset(urls, 0, 32*sizeof(char*));
urlidx = 0;
newidx = MAX_URL_PATHS;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerror);
//We want to consider error conditions fatal, rather than
//passing error text as data
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
memset(filename, 0, MAX_FILE_LEN);
for (i=0; i < argc; i++) {
if (strstr(argv[i], ":") > 0) {
if (j < MAX_URL_PATHS) {
urls[j] = argv[i];
tmp = strrchr(urls[j++], '/');
strncpy(filename, tmp, MAX_FILE_LEN);
}
//Request single threaded mode, as curl would need more
// filehandles for multithread
argv[i] = "-s";
}
}
if (filename[0] == 0) {
fprintf(stderr, "No URL given in arguments\n");
exit(1);
}
for (i=0; urls[i] != NULL; i++) {
printf("Registering mount path: %s\n", urls[i]);
}
j = urlidx;
printf("Connecting to %s\n", urls[urlidx]);
curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]);
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
while (curl_easy_perform(curl) != CURLE_OK) {
fprintf(stderr, "urlmount: error while communicating with %s: %s\n", urls[urlidx++], curlerror);
if (urls[urlidx] == NULL)
urlidx = 0;
if (urlidx == j) {
fprintf(stderr, "urlmount: Unable to reach any target url, aborting\n");
exit(1);
}
printf("Connecting to %s\n", urls[urlidx]);
curl_easy_setopt(curl, CURLOPT_URL, urls[urlidx]);
}
printf("Successfully connected to %s\n", urls[urlidx]);
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &fsize);
filesize = round(fsize);
curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
if (filesize < 1) {
fprintf(stderr, "Unable to reach designated URL\n");
exit(1);
}
if (!curl) {
fprintf(stderr, "Unable to initialize CURL!\n");
exit(1);
}
curl_easy_cleanup(curl);
curl_global_cleanup();
fuse_main(argc, argv, &http_ops, NULL);
}
+1 -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, python3-eventlet, python3-pyparsing, python3-pyte, python3-pyghmi, python3-paramiko/' debian/control
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-eventlet, python3-pyparsing, python3-pyghmi, python3-paramiko/' debian/control
fi
if grep wheezy /etc/os-release; then
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
+32 -6
View File
@@ -26,6 +26,7 @@ import Cryptodome.Protocol.KDF as KDF
from fnmatch import fnmatch
import hashlib
import hmac
import msgpack
import multiprocessing
import os
import pwd
@@ -100,6 +101,10 @@ _deniedbyrole = {
}
class PromptsNeeded(Exception):
def __init__(self, prompts):
self.prompts = prompts
def _get_usertenant(name, tenant=False):
"""_get_usertenant
@@ -221,11 +226,14 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
if ucfg is None:
eventlet.sleep(0.05)
return None
bpassphrase = None
if isinstance(passphrase, dict) and len(passphrase) == 1:
passphrase = list(passphrase.values())[0]
if isinstance(passphrase, bytes):
bpassphrase = passphrase
else:
elif not isinstance(passphrase, dict):
bpassphrase = passphrase.encode('utf8')
if (user, tenant) in _passcache:
if (user, tenant) in _passcache and bpassphrase:
if hashlib.sha256(bpassphrase).digest() == _passcache[(user, tenant)]:
return authorize(user, element, tenant, operation=operation)
else:
@@ -233,7 +241,7 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
# while someone is legitimately logged in
# invalidate cache and force the slower check
del _passcache[(user, tenant)]
if 'cryptpass' in ucfg:
if 'cryptpass' in ucfg and bpassphrase:
_passchecking[(user, tenant)] = True
# TODO(jbjohnso): WORKERPOOL
# PBKDF2 is, by design, cpu intensive
@@ -275,23 +283,41 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
# to let a non-0 user check anothers password.
# We will fork and the child will assume elevated privilege to
# get unix_chkpwd helper to enable checking /etc/shadow
getprompt, sendprompt = os.pipe()
getprompt, sendprompt = os.fdopen(getprompt, 'rb', 0), os.fdopen(sendprompt, 'wb', 0)
pid = os.fork()
if not pid:
usergood = False
try:
getprompt.close()
# we change to the uid we are trying to authenticate as, because
# pam_unix uses unix_chkpwd which reque
os.setuid(pwe.pw_uid)
usergood = pam.authenticate(user, passphrase, service=_pamservice)
pa = pam.pam()
usergood = pa.authenticate(user, passphrase, service=_pamservice)
if (not usergood and len(pa.prompts) > 1 and
(not isinstance(passphrase, dict) or
(set(passphrase) - pa.prompts))):
sendprompt.write(msgpack.packb(list(pa.prompts)))
sendprompt.close()
os._exit(2)
finally:
os._exit(0 if usergood else 1)
usergood = os.waitpid(pid, 0)[1] == 0
sendprompt.close()
usergood = os.waitpid(pid, 0)[1]
if (usergood >> 8) == 2:
prompts = getprompt.read()
if (prompts):
raise PromptsNeeded(msgpack.unpackb(prompts))
usergood = usergood == 0
getprompt.close()
else:
# We are running as root, we don't need to fork in order to authenticate the
# user
usergood = pam.authenticate(user, passphrase, service=_pamservice)
if usergood:
_passcache[(user, tenant)] = hashlib.sha256(bpassphrase).digest()
if bpassphrase:
_passcache[(user, tenant)] = hashlib.sha256(bpassphrase).digest()
return authorize(user, element, tenant, operation, skipuserobj=False)
eventlet.sleep(0.05) # stall even on test for existence of a username
return None
+64 -22
View File
@@ -66,33 +66,29 @@ def get_certificate_paths():
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 = shortname # 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)
def assure_tls_ca():
keyout, certout = ('/etc/confluent/tls/cakey.pem', '/etc/confluent/tls/cacert.pem')
if os.path.exists(certout):
return
try:
os.makedirs('/etc/confluent/tls')
except OSError as e:
if e.errno != 17:
raise
sslcfg = get_openssl_conf_location()
tmpconfig = tempfile.mktemp()
shutil.copy2(sslcfg, tmpconfig)
subprocess.check_call(
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
keyout])
try:
with open(tmpconfig, 'a') as cfgfile:
cfgfile.write('\n[SAN]i\nbasicConstraints = CA:true\nsubjectAltName={0}'.format(san))
cfgfile.write('\n[CACert]\nbasicConstraints = CA:true\n')
subprocess.check_call([
'openssl', 'req', '-new', '-x509', '-key', keyout, '-days',
'7300', '-out', certout, '-subj', '/CN={0}'.format(longname),
'-extensions', 'SAN', '-config', tmpconfig
'27300', '-out', certout, '-subj',
'/CN=Confluent TLS Certificate authority ({0})'.format(socket.gethostname()),
'-extensions', 'CACert', '-config', tmpconfig
])
finally:
os.remove(tmpconfig)
@@ -104,9 +100,9 @@ def create_certificate(keyout=None, certout=None):
except OSError as e:
if e.errno != 17:
raise
shutil.copy2(certout, fname)
shutil.copy2('/etc/confluent/tls/cacert.pem', fname)
hv = subprocess.check_output(
['openssl', 'x509', '-in', certout, '-hash', '-noout'])
['openssl', 'x509', '-in', '/etc/confluent/tls/cacert.pem', '-hash', '-noout'])
if not isinstance(hv, str):
hv = hv.decode('utf8')
hv = hv.strip()
@@ -123,6 +119,52 @@ def create_certificate(keyout=None, certout=None):
pass
os.symlink(certname, hashname)
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')
assure_tls_ca()
shortname = socket.gethostname().split('.')[0]
longname = shortname # 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()
extconfig = tempfile.mktemp()
csrout = tempfile.mktemp()
shutil.copy2(sslcfg, tmpconfig)
try:
with open(tmpconfig, 'a') as cfgfile:
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
with open(extconfig, 'a') as cfgfile:
cfgfile.write('\nbasicConstraints=CA:false\nsubjectAltName={0}'.format(san))
subprocess.check_call([
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj',
'/CN={0}'.format(longname),
'-extensions', 'SAN', '-config', tmpconfig
])
subprocess.check_call([
'openssl', 'x509', '-req', '-in', csrout,
'-CA', '/etc/confluent/tls/cacert.pem',
'-CAkey', '/etc/confluent/tls/cakey.pem',
'-set_serial', '0x123', '-out', certout, '-days', '27300',
'-extfile', extconfig
])
finally:
os.remove(tmpconfig)
os.remove(csrout)
os.remove(extconfig)
if __name__ == '__main__':
outdir = os.getcwd()
keyout = os.path.join(outdir, 'key.pem')
@@ -244,6 +244,7 @@ def get_myname():
def handle_connection(connection, cert, request, local=False):
global currentleader
global retrythread
global initting
connection.settimeout(5)
operation = request['operation']
if cert:
@@ -395,6 +396,11 @@ def handle_connection(connection, cert, request, local=False):
tlvdata.send(connection, {'error': 'Invalid token'})
connection.close()
return
if not list(cfm.list_collective()):
# First enrollment of a collective, since the collective doesn't
# quite exist, then set initting false to let the enrollment action
# drive this particular initialization
initting = False
myrsp = base64.b64encode(myrsp)
fprint = util.get_fingerprint(cert)
myfprint = util.get_fingerprint(mycert)
@@ -461,6 +461,26 @@ node = {
'case for other net attributes, net.eth0.ipv4_gateway '
'and similar is accepted.'
},
'net.ipv6_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. "/64". If not '
'specified, it will attempt to autodetect based on '
'current network configuration.'
},
'net.ipv6_method': {
'description': 'Whether to use static or dhcp when configuring this '
'interface for IPv6. "firmwaredhcp" means to defer to '
'external DHCP server during firmware execution, but '
'use static for OS',
'validvalues': ('dhcp', 'static', 'firmwaredhcp', 'none')
},
'net.ipv6_gateway': {
'description': 'The IPv6 gateway to use if applicable. As is the '
'case for other net attributes, net.eth0.ipv6_gateway '
'and similar is accepted.'
},
'net.hwaddr': {
'description': 'The hardware address, aka MAC address of the interface indicated, generally populated by the '
'PXE discovery mechanism'
@@ -143,6 +143,7 @@ class ConsoleHandler(object):
#self.termstream.attach(self.buffer)
self.livesessions = set([])
self.utf8decoder = codecs.getincrementaldecoder('utf-8')()
self.pendingbytes = None
if self._logtobuffer:
self.logger = log.Logger(node, console=True,
tenant=configmanager.tenant)
@@ -186,8 +187,15 @@ class ConsoleHandler(object):
def feedbuffer(self, data):
if not isinstance(data, bytes):
data = data.encode('utf-8')
if self.pendingbytes is not None:
self.pendingbytes += data
self.pendingbytes = b''
try:
send_output(self.node, data)
data = self.pendingbytes
self.pendingbytes = None
if data:
send_output(self.node, data)
except Exception:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
@@ -650,6 +658,7 @@ class ProxyConsole(object):
while data:
self.data_handler(data)
data = tlvdata.recv(self.remote)
self.remote.close()
def get_buffer_age(self):
# the server sends a buffer age if appropriate, no need to handle
+20 -7
View File
@@ -77,13 +77,15 @@ try:
except NameError:
unicode = str
def seek_element(currplace, currkey):
def seek_element(currplace, currkey, depth):
try:
return currplace[currkey]
except TypeError:
if isinstance(currplace, PluginCollection):
# we hit a plugin curated collection, all children
# are up to the plugin to comprehend
if currplace.maxdepth and depth > currplace.maxdepth:
raise
return currplace
raise
@@ -91,8 +93,9 @@ def seek_element(currplace, currkey):
def nested_lookup(nestdict, key):
try:
currloc = nestdict
for currk in key:
currloc = seek_element(currloc, currk)
for i in range(len(key)):
currk = key[i]
currloc = seek_element(currloc, currk, len(key) - i)
return currloc
except TypeError:
raise exc.NotFoundException("Invalid element requested")
@@ -143,8 +146,9 @@ class PluginRoute(object):
class PluginCollection(object):
def __init__(self, routedict):
def __init__(self, routedict, maxdepth=1):
self.routeinfo = routedict
self.maxdepth = maxdepth
def handle_deployment(configmanager, inputdata, pathcomponents,
@@ -262,6 +266,10 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'location': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'ntp': {
'enabled': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
@@ -349,6 +357,10 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'graphical': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
'description': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
@@ -813,6 +825,7 @@ def handle_dispatch(connection, cert, dispatch, peername):
_forward_rsp(connection, res)
keepalive.kill()
connection.sendall('\x00\x00\x00\x00\x00\x00\x00\x00')
connection.close()
def _forward_rsp(connection, res):
@@ -1041,12 +1054,12 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE,
keyfile='/etc/confluent/privkey.pem',
certfile='/etc/confluent/srvcert.pem')
except Exception:
except Exception as e:
for node in nodes:
if a:
yield msg.ConfluentResourceUnavailable(
node, 'Collective member {0} is unreachable'.format(
a['name']))
node, 'Collective member {0} is unreachable ({1})'.format(
a['name'], str(e)))
else:
yield msg.ConfluentResourceUnavailable(
node,
+39 -8
View File
@@ -711,13 +711,25 @@ def detected(info):
nodename, info['maccount'] = get_nodename(cfg, handler, info)
if nodename and handler and handler.https_supported:
dp = cfg.get_node_attributes([nodename],
('pubkeys.tls_hardwaremanager',))
lastfp = dp.get(nodename, {}).get('pubkeys.tls_hardwaremanager',
('pubkeys.tls_hardwaremanager', 'id.uuid', 'discovery.policy'))
dp = dp.get(nodename, {})
lastfp = dp.get('pubkeys.tls_hardwaremanager',
{}).get('value', None)
if util.cert_matches(lastfp, handler.https_cert):
info['nodename'] = nodename
known_nodes[nodename][info['hwaddr']] = info
info['discostatus'] = 'discovered'
uuid = info.get('uuid', None)
if uuid:
storeuuid = dp.get('id.uuid', {}).get('value', None)
if not storeuuid:
discop = dp.get('discovery.policy', {}).get('value', '')
if discop:
policies = set(discop.split(','))
else:
policies = set([])
if policies & {'open', 'permissive'}:
cfg.set_node_attributes({nodename: {'id.uuid': info['uuid']}})
return # already known, no need for more
#TODO(jjohnson2): We might have to get UUID for certain searches...
#for now defer probe until inside eval_node. We might not have
@@ -850,6 +862,9 @@ def get_nodename(cfg, handler, info):
if nodename is None:
_map_unique_ids()
nodename = nodes_by_uuid.get(curruuid, None)
if not nodename and info['handler'] == pxeh:
enrich_pxe_info(info)
nodename = info.get('nodename', None)
if not nodename:
# Ok, see if it is something with a chassis-uuid and discover by
# chassis
@@ -912,6 +927,13 @@ def get_nodename_from_chained_smms(cfg, handler, info):
nodename = newnodename
return nodename
def get_node_guess_by_uuid(uuid):
for mac in known_uuids.get(uuid, {}):
nodename = known_uuids[uuid][mac].get('nodename', None)
if nodename:
return nodename
return None
def get_node_by_uuid_or_mac(uuidormac):
node = pxe.macmap.get(uuidormac, None)
if node is not None:
@@ -1090,7 +1112,7 @@ def discover_node(cfg, handler, info, nodename, manual):
del unknown_info[info['hwaddr']]
info['discostatus'] = 'identified'
dp = cfg.get_node_attributes(
[nodename], ('discovery.policy',
[nodename], ('discovery.policy', 'id.uuid',
'pubkeys.tls_hardwaremanager'))
policy = dp.get(nodename, {}).get('discovery.policy', {}).get(
'value', None)
@@ -1163,10 +1185,19 @@ def discover_node(cfg, handler, info, nodename, manual):
except KeyError:
pass
return True
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
'value allowing discovery (open or permissive)'.format(
nodename)})
info['discofailure'] = 'policy'
if info['handler'] == pxeh:
olduuid = dp.get(nodename, {}).get('id.uuid', {}).get(
'value', None)
if olduuid.lower() != info['uuid']:
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
'value allowing discovery (open, permissive, or pxe)'.format(
nodename)})
info['discofailure'] = 'policy'
else:
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
'value allowing discovery (open or permissive)'.format(
nodename)})
info['discofailure'] = 'policy'
return False
@@ -1317,7 +1348,7 @@ def stop_autosense():
def start_autosense():
autosensors.add(eventlet.spawn(slp.snoop, safe_detected, slp))
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected, pxe))
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected, pxe, get_node_guess_by_uuid))
nodes_by_fprint = {}
@@ -340,6 +340,10 @@ class NodeHandler(immhandler.NodeHandler):
rsp, status = wc.grab_json_response_with_status(
'/api/function',
{'USER_UserModify': '{0},{1},,1,Administrator,0,0,0,0,,8,'.format(uid, username)})
elif status == 200 and rsp.get('return', 0) == 13:
rsp, status = wc.grab_json_response_with_status(
'/api/function',
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,,,'.format(uid, username)})
self.tmppasswd = None
self._currcreds = (username, passwd)
@@ -25,8 +25,10 @@
import confluent.config.configmanager as cfm
import confluent.collective.manager as collective
import confluent.noderange as noderange
import confluent.neighutil as neighutil
import confluent.log as log
import confluent.netutil as netutil
import confluent.util as util
import ctypes
import ctypes.util
import eventlet
@@ -34,13 +36,20 @@ import eventlet.green.socket as socket
import eventlet.green.select as select
import netifaces
import struct
import time
import traceback
import uuid
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'
ignoremacs = {}
ignoredisco = {}
mcastv6addr = 'ff02::1:2'
def _ipsum(data):
currsum = 0
@@ -186,6 +195,26 @@ def _decode_ocp_vivso(rq, idx, size):
idx += rq[idx + 1] + 2
return '', None, vivso
def v6opts_to_dict(rq):
optidx = 0
reqdict = {}
disco = {'uuid':None, 'arch': None, 'vivso': None}
try:
while optidx < len(rq):
optnum, optlen = struct.unpack('!HH', rq[optidx:optidx+4])
reqdict[optnum] = rq[optidx + 4:optidx + 4 + optlen]
optidx += optlen + 4
except IndexError:
pass
if 1 in reqdict:
duid = reqdict[1]
if struct.unpack('!H', duid[:2])[0] == 4:
disco['uuid'] = decode_uuid(duid[2:])
if 61 in reqdict:
arch = bytes(rq[optidx+4:optidx+4+optlen])
disco['arch'] = pxearchs.get(bytes(reqdict[61]), None)
return reqdict, disco
def opts_to_dict(rq, optidx, expectype=1):
reqdict = {}
disco = {'uuid':None, 'arch': None, 'vivso': None}
@@ -298,7 +327,7 @@ def start_proxydhcp():
eventlet.spawn_n(proxydhcp)
def snoop(handler, protocol=None):
def snoop(handler, protocol=None, nodeguess=None):
#TODO(jjohnson2): ipv6 socket and multicast for DHCPv6, should that be
#prominent
#TODO(jjohnson2): enable unicast replies. This would suggest either
@@ -314,94 +343,130 @@ def snoop(handler, protocol=None):
net4.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
net4.setsockopt(socket.IPPROTO_IP, IP_PKTINFO, 1)
net4.bind(('', 67))
v6addr = socket.inet_pton(socket.AF_INET6, mcastv6addr)
net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
for ifidx in util.list_interface_indexes():
v6grp = v6addr + struct.pack('=I', ifidx)
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, v6grp)
net6.bind(('', 547))
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
while True:
try:
# Just need some delay, picked a prime number so that overlap with other
# timers might be reduced, though it really is probably nothing
ready = select.select([net4], [], [], None)
ready = select.select([net4, net6], [], [], 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 i < 64:
continue
#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
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
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]
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
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)
for netc in ready[0]:
idx = None
if netc == net4:
i = recvmsg(netc.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 i < 64:
continue
_, level, typ = struct.unpack('QII', cmsgarr[:16])
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
idx, recv = struct.unpack('II', cmsgarr[16:24])
recv = ipfromint(recv)
rqv = memoryview(rawbuffer)
if rawbuffer[0] == 1: # Boot request
process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv)
elif netc == net6:
recv = 'ff02::1:2'
pkt, addr = netc.recvfrom(2048)
idx = addr[-1]
i = len(pkt)
if i < 64:
continue
rqv = memoryview(pkt)
rq = bytearray(rqv[:2])
if rq[0] in (1, 3): # dhcpv6 solicit
process_dhcp6req(handler, rqv, addr, netc, cfg, nodeguess)
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
def process_dhcp6req(handler, rqv, addr, net, cfg, nodeguess):
ip = addr[0]
req, disco = v6opts_to_dict(bytearray(rqv[4:]))
req['txid'] = rqv[1:4]
req['rqtype'] = bytearray(rqv[:1])[0]
if not disco.get('uuid', None) or not disco.get('arch', None):
return
mac = neighutil.get_hwaddr(ip.split('%', 1)[0])
if not mac:
net.sendto(b'\x00', addr)
tries = 5
while tries and not mac:
eventlet.sleep(0.01)
tries -= 1
mac = neighutil.get_hwaddr(ip.split('%', 1)[0])
info = {'hwaddr': mac, 'uuid': disco['uuid'],
'architecture': disco['arch'], 'services': ('pxe-client',)}
if ignoredisco.get(mac, 0) + 90 < time.time():
ignoredisco[mac] = time.time()
handler(info)
consider_discover(info, req, net, cfg, None, nodeguess, addr)
def process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv):
rq = bytearray(rqv)
addrlen = rq[2]
if addrlen > 16 or addrlen == 0:
return
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:
return
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]
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', '')]}}
if time.time() > ignoredisco.get(netaddr, 0) + 90:
ignoredisco[netaddr] = time.time()
handler(info)
#consider_discover(info, rqinfo, net4, cfg, rqv)
return
# 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
info = {'hwaddr': netaddr, 'uuid': disco['uuid'],
'architecture': disco['arch'],
'netinfo': {'ifidx': idx, 'recvip': recv, 'txid': txid},
'services': ('pxe-client',)}
if (disco['uuid']
and time.time() > ignoredisco.get(netaddr, 0) + 90):
ignoredisco[netaddr] = time.time()
handler(info)
consider_discover(info, rqinfo, net4, cfg, rqv, nodeguess)
def clear_nodes(nodes):
@@ -451,13 +516,105 @@ def get_deployment_profile(node, cfg, cfd=None):
staticassigns = {}
myipbypeer = {}
def check_reply(node, info, packet, sock, cfg, reqview):
def check_reply(node, info, packet, sock, cfg, reqview, addr):
httpboot = info['architecture'] == 'uefi-httpboot'
replen = 275 # default is going to be 286
cfd = cfg.get_node_attributes(node, ('deployment.*'))
profile = get_deployment_profile(node, cfg, cfd)
if not profile:
if time.time() > ignoremacs.get(info['hwaddr'], 0) + 90:
ignoremacs[info['hwaddr']] = time.time()
log.log({'info': 'Ignoring boot attempt by {0} no deployment profile specified (uuid {1}, hwaddr {2})'.format(
node, info['uuid'], info['hwaddr']
)})
return
if addr:
if not httpboot:
log.log({'info': 'IPv6 PXE boot attempt by {0}, but IPv6 PXE is not supported, try IPv6 HTTP boot or IPv4 boot'.format(node)})
return
return reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock)
else:
return reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile)
def reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock):
myaddrs = netutil.get_my_addresses(addr[-1], socket.AF_INET6)
if not myaddrs:
log.log({'info': 'Unable to provide IPv6 boot services to {0}, no viable IPv6 configuration on interface index "{1}" to respond through.'.format(node, addr[-1])})
return
niccfg = netutil.get_nic_config(cfg, node, ifidx=addr[-1])
ipv6addr = niccfg.get('ipv6_address', None)
ipv6prefix = niccfg.get('ipv6_prefix', None)
ipv6method = niccfg.get('ipv6_method', 'static')
ipv6srvaddr = niccfg.get('deploy_server_v6', None)
if not ipv6srvaddr:
log.log({'info': 'Unable to determine an appropriate ipv6 server ip for {}'.format(node)})
return
insecuremode = cfd.get(node, {}).get('deployment.useinsecureprotocols',
{}).get('value', 'never')
if not insecuremode:
insecuremode = 'never'
proto = 'https' if insecuremode == 'never' else 'http'
bootfile = '{0}://[{1}]/confluent-public/os/{2}/boot.img'.format(
proto, ipv6srvaddr, profile
)
if not isinstance(bootfile, bytes):
bootfile = bootfile.encode('utf8')
ipass = []
if ipv6method not in ('dhcp', 'firmwaredhcp') and ipv6addr:
if not ipv6prefix:
log.log({'info': 'Unable to determine prefix to serve to address {} for node {}'.format(ipv6addr, node)})
return
ipass = bytearray(40)
ipass[:4] = packet[3][:4] # pass iaid back
ipass[4:16] = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18'
ipass[16:32] = socket.inet_pton(socket.AF_INET6, ipv6addr)
ipass[32:40] = b'\x00\x00\x00\x78\x00\x00\x01\x2c'
#1 msgtype
#3 txid
#22 - server ident
#len(packet[1]) + 4 - client ident
#len(ipass) + 4 or 0
#len(url) + 4
replylen = 50 + len(bootfile) + len(packet[1]) + 4
if len(ipass):
replylen += len(ipass)
reply = bytearray(replylen)
reply[0] = 2 if packet['rqtype'] == 1 else 7
reply[1:4] = packet['txid']
offset = 4
struct.pack_into('!HH', reply, offset, 1, len(packet[1]))
offset += 4
reply[offset:offset+len(packet[1])] = packet[1]
offset += len(packet[1])
struct.pack_into('!HHH', reply, offset, 2, 18, 4)
offset += 6
reply[offset:offset+16] = get_my_duid()
offset += 16
if ipass:
struct.pack_into('!HH', reply, offset, 3, len(ipass))
offset += 4
reply[offset:offset + len(ipass)] = ipass
offset += len(ipass)
struct.pack_into('!HH', reply, offset, 59, len(bootfile))
offset += 4
reply[offset:offset + len(bootfile)] = bootfile
offset += len(bootfile)
# Need the HTTPClient in the vendor class for reply
struct.pack_into('!HHIH', reply, offset, 16, 16, 0, 10)
offset += 10
reply[offset:offset + 10] = b'HTTPClient'
sock.sendto(reply, addr)
_myuuid = None
def get_my_duid():
global _myuuid
if not _myuuid:
_myuuid = uuid.uuid4().bytes
return _myuuid
def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile):
replen = 275 # default is going to be 286
myipn = info['netinfo']['recvip']
myipn = socket.inet_aton(myipn)
@@ -631,13 +788,40 @@ def ack_request(pkt, rq, info):
repview[26:28] = struct.pack('!H', datasum)
send_raw_packet(repview, len(rply), rq, info)
def consider_discover(info, packet, sock, cfg, reqview):
def consider_discover(info, packet, sock, cfg, reqview, nodeguess, addr=None):
if info.get('hwaddr', None) in macmap and info.get('uuid', None):
check_reply(macmap[info['hwaddr']], info, packet, sock, cfg, reqview)
check_reply(macmap[info['hwaddr']], info, packet, sock, cfg, reqview, addr)
elif info.get('uuid', None) in uuidmap:
check_reply(uuidmap[info['uuid']], info, packet, sock, cfg, reqview)
check_reply(uuidmap[info['uuid']], info, packet, sock, cfg, reqview, addr)
elif packet.get(53, None) == b'\x03':
ack_request(packet, reqview, info)
elif info.get('uuid', None) and info.get('hwaddr', None):
if time.time() > ignoremacs.get(info['hwaddr'], 0) + 90:
ignoremacs[info['hwaddr']] = time.time()
maybenode = None
if nodeguess:
maybenode = nodeguess(info['uuid'])
if maybenode:
# originally was going to just offer up the node (the likely
# scenario is that it was manually added, autodiscovery picked
# up the TLS match, and correlated)
# However, since this is technically unverified data, we shouldn't
# act upon it until confirmed by process or user
# So instead, offer a hint about what is probably the case, but
# hasn't yet been approved by anything
log.log(
{'info': 'Boot attempt from uuid {0} or hardware '
'address {1}, which is not confirmed to be a '
'node, but seems to be {2}. To confirm node '
'identity, \'nodediscover reassign -n {2}\' or '
'\'nodeattrib {2} id.uuid={0}\''.format(
info['uuid'], info['hwaddr'], maybenode
)})
else:
log.log(
{'info': 'No node matches boot attempt from uuid {0} or hardware address {1}'.format(
info['uuid'], info['hwaddr']
)})
if __name__ == '__main__':
@@ -106,12 +106,9 @@ def _parse_slp_packet(packet, peer, rsps, xidmap):
addr = peer[0]
if '%' in addr:
addr = addr[:addr.index('%')]
mac = None
if addr not in neighutil.neightable:
neighutil.update_neigh()
if addr in neighutil.neightable:
identifier = neighutil.neightable[addr]
mac = identifier
mac = neighutil.get_hwaddr(addr)
if mac:
identifier = mac
else:
identifier = addr
if (identifier, parsed['xid']) in rsps:
@@ -254,7 +251,6 @@ def _grab_rsps(socks, rsps, interval, xidmap):
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
neighutil.refresh_neigh()
_parse_slp_packet(rsp, peer, rsps, xidmap)
res = select.select(socks, (), (), interval)
if not res:
@@ -415,11 +411,12 @@ def rescan(handler):
known_peers = set([])
for scanned in scan():
for addr in scanned['addresses']:
ip = addr[0].partition('%')[0] # discard scope if present
if ip not in neighutil.neightable:
continue
if addr in known_peers:
break
ip = addr[0].partition('%')[0] # discard scope if present
macaddr = neighutil.get_hwaddr(ip)
if not macaddr:
continue
known_peers.add(addr)
else:
handler(scanned)
@@ -484,12 +481,10 @@ def snoop(handler, protocol=None):
ip = peer[0].partition('%')[0]
if peer in known_peers:
continue
if ip not in neighutil.neightable:
neighutil.update_neigh()
if ip not in neighutil.neightable:
mac = neighutil.get_hwaddr(ip)
if not mac:
continue
known_peers.add(peer)
mac = neighutil.neightable[ip]
if mac in peerbymacaddress:
peerbymacaddress[mac]['addresses'].append(peer)
else:
@@ -541,13 +536,12 @@ def active_scan(handler, protocol=None):
known_peers = set([])
for scanned in scan():
for addr in scanned['addresses']:
ip = addr[0].partition('%')[0] # discard scope if present
if ip not in neighutil.neightable:
neighutil.update_neigh()
if ip not in neighutil.neightable:
continue
if addr in known_peers:
break
ip = addr[0].partition('%')[0] # discard scope if present
macaddr = neighutil.get_hwaddr(ip)
if not macaddr:
continue
known_peers.add(addr)
else:
scanned['protocol'] = protocol
@@ -61,13 +61,12 @@ def active_scan(handler, protocol=None):
known_peers = set([])
for scanned in scan(['urn:dmtf-org:service:redfish-rest:1']):
for addr in scanned['addresses']:
ip = addr[0].partition('%')[0] # discard scope if present
if ip not in neighutil.neightable:
neighutil.update_neigh()
if ip not in neighutil.neightable:
continue
if addr in known_peers:
break
ip = addr[0].partition('%')[0] # discard scope if present
hwaddr = neighutil.get_hwaddr(ip)
if not hwaddr:
continue
known_peers.add(addr)
else:
scanned['protocol'] = protocol
@@ -134,11 +133,9 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
ip = peer[0].partition('%')[0]
if peer in known_peers:
continue
if ip not in neighutil.neightable:
neighutil.update_neigh()
if ip not in neighutil.neightable:
mac = neighutil.get_hwaddr(ip)
if not mac:
continue
mac = neighutil.neightable[ip]
known_peers.add(peer)
newmacs.add(mac)
if mac in peerbymacaddress:
@@ -207,7 +204,7 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
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 '%' in peer[0]:
iface = peer[0].split('%', 1)[1]
iface = socket.getaddrinfo(peer[0], 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][-1]
reply += 'MGTIFACE: {0}\r\n'.format(
peer[0].split('%', 1)[1])
ncfg = netutil.get_nic_config(
@@ -284,7 +281,6 @@ def _find_service(service, target):
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
neighutil.refresh_neigh()
_parse_ssdp(peer, rsp, peerdata)
timeout = deadline - util.monotonic_time()
if timeout < 0:
@@ -316,11 +312,9 @@ def _parse_ssdp(peer, rsp, peerdata):
ip = peer[0].partition('%')[0]
nid = ip
mac = None
if ip not in neighutil.neightable:
neighutil.update_neigh()
if ip in neighutil.neightable:
nid = neighutil.neightable[ip]
mac = nid
mac = neighutil.get_hwaddr(ip)
if mac:
nid = mac
headlines = rsp.split(b'\r\n')
try:
_, code, _ = headlines[0].split(b' ', 2)
@@ -138,7 +138,7 @@ class Updater(object):
def remove_updates(nodes, tenant, element, type='firmware'):
if len(element) < 5 and element[:2] != ['media', 'uploads']:
if len(element) < 5 and element[:2] not in (['media', 'uploads'], ['support', 'servicedata']):
raise exc.InvalidArgumentException()
upid = element[-1]
if type == 'firmware':
+21 -4
View File
@@ -305,9 +305,19 @@ def _authorize_request(env, operation):
# of a CSRF
return {'code': 401}
return ('logout',)
name, passphrase = base64.b64decode(
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1)
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
if env['HTTP_AUTHORIZATION'].startswith('MultiBasic '):
name, passphrase = base64.b64decode(
env['HTTP_AUTHORIZATION'].replace('MultiBasic ', '')).split(b':', 1)
passphrase = json.loads(passphrase)
else:
name, passphrase = base64.b64decode(
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1)
try:
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
except Exception as e:
if hasattr(e, 'prompts'):
return {'code': 403, 'prompts': e.prompts}
raise
if authdata is False:
return {'code': 403}
elif not authdata:
@@ -519,7 +529,14 @@ def resourcehandler_backend(env, start_response):
return
if authorized['code'] == 403:
start_response('403 Forbidden', badauth)
yield 'Forbidden'
response = {'result': 'Forbidden'}
if 'prompts' in authorized:
response['prompts'] = []
for prompt in authorized['prompts']:
if not isinstance(prompt, str):
prompt = prompt.decode('utf8')
response['prompts'].append(prompt)
yield json.dumps(response)
return
if authorized['code'] != 200:
raise Exception("Unrecognized code from auth engine")
+22
View File
@@ -59,6 +59,7 @@ import gc
from greenlet import greenlet
import sys
import os
import glob
import signal
import socket
import time
@@ -209,6 +210,26 @@ def setlimits():
except Exception:
pass
def assure_ownership(path):
try:
if os.getuid() != os.stat(path).st_uid:
sys.stderr.write('{} is not owned by confluent user, change ownership\n'.format(path))
sys.exit(1)
except OSError as e:
if e.errno == 13:
sys.stderr.write('{} is not owned by confluent user, change ownership\n'.format(path))
sys.exit(1)
def sanity_check():
if os.getuid() == 0:
return True
assure_ownership('/etc/confluent')
assure_ownership('/etc/confluent/cfg')
for filename in glob.glob('/etc/confluent/cfg/*'):
assure_ownership(filename)
assure_ownership('/etc/confluent/privkey.pem')
assure_ownership('/etc/confluent/srvcert.pem')
def run(args):
setlimits()
@@ -226,6 +247,7 @@ def run(args):
sys.stderr.write("Error unlocking credential store\n")
doexit()
sys.exit(1)
sanity_check()
try:
confluentcore.load_plugins()
except:
+17
View File
@@ -199,6 +199,8 @@ class ConfluentMessage(object):
notes.append('Broken because of %s' % val['broken'])
elif val is not None and 'expression' in val:
value = val['expression']
elif isinstance(val, dict):
value = _htmlify_structure(val)
if value is None:
value = ''
if val is not None and value == '' and 'isset' in val and val[
@@ -298,6 +300,7 @@ class DeletedResource(ConfluentMessage):
notnode = True
def __init__(self, resource):
self.myargs = [resource]
self.desc = 'Delete Resource'
self.kvpairs = {'deleted': resource}
def strip_node(self, node):
@@ -310,6 +313,7 @@ class CreatedResource(ConfluentMessage):
def __init__(self, resource):
self.myargs = [resource]
self.desc = 'Create Resource'
self.kvpairs = {'created': resource}
def strip_node(self, node):
@@ -1791,6 +1795,19 @@ class License(ConfluentMessage):
else:
self.kvpairs = {name: {'License': kv}}
class GraphicalConsole(ConfluentMessage):
readonly = True
def __init__(self, name=None, mime=None, data=None):
self.notnode = name is None
self.desc = 'Graphical console launcher'
kv = []
kv.append({'mime': mime, 'data': data})
if self.notnode:
self.kvpairs = {'Launcher': kv}
else:
self.kvpairs = {name: {'Launcher': kv}}
class CryptedAttributes(Attributes):
defaulttype = 'password'
+69 -33
View File
@@ -15,52 +15,88 @@
# limitations under the License.
# A consolidated manage of neighbor table information management.
# Ultimately, this should use AF_NETLINK, but in the interest of time,
# use ip neigh for the moment
import confluent.util as util
import eventlet.green.subprocess as subprocess
import os
import eventlet.semaphore as semaphore
import eventlet.green.socket as socket
import struct
neightable = {}
neightime = 0
import re
_validmac = re.compile('..:..:..:..:..:..')
neighlock = semaphore.Semaphore()
def update_neigh():
def _update_neigh():
global neightable
global neightime
neightable = {}
if os.name == 'nt':
return
ipn = subprocess.Popen(['ip', 'neigh'], stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(neighdata, err) = ipn.communicate()
neighdata = util.stringify(neighdata)
for entry in neighdata.split('\n'):
entry = entry.split(' ')
if len(entry) < 5 or not entry[4]:
continue
if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'):
# Note that these addresses are common static ip addresses
# that are hopelessly ambiguous if there are many
# so ignore such entries and move on
# ideally the system network steers clear of this landmine of
# a subnet, but just in case
continue
if not _validmac.match(entry[4]):
continue
neightable[entry[0]] = entry[4]
neightime = os.times()[4]
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
s.bind((0, 0))
# RTM_GETNEIGH
# nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid
nlhdr = b'\x1c\x00\x00\x00\x1e\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00'
# ndmsg struct u8 family u8 pad, u16 pad, s32 ifidx, u16 state, u8 flags, u8 type
ndmsg= b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
s.sendall(nlhdr + ndmsg)
neightable = {}
try:
while True:
pdata = s.recv(65536)
v = memoryview(pdata)
if struct.unpack('H', v[4:6])[0] == 3:
break
while len(v):
length, typ = struct.unpack('IH', v[:6])
if typ == 28:
hlen = struct.calcsize('BIHBB')
_, idx, state, flags, typ = struct.unpack('BIHBB', v[16:16+hlen])
if typ == 1: # only handle unicast entries
curraddr = None
currip = None
rta = v[16+hlen:length]
while len(rta):
rtalen, rtatyp = struct.unpack('HH', rta[:4])
if rtatyp == 2: # hwaddr
curraddr = rta[4:rtalen].tobytes()
if len(curraddr) == 20:
curraddr = curraddr[12:]
elif rtatyp == 1: # ip address
currip = rta[4:rtalen].tobytes()
rta = rta[rtalen:]
if not rtalen:
break
if curraddr and currip:
neightable[currip] = curraddr
v = v[length:]
finally:
s.close()
def refresh_neigh():
global neightime
def get_hwaddr(ipaddr):
hwaddr = None
if os.name == 'nt':
return
if os.times()[4] > (neightime + 30):
update_neigh()
return hwaddr
if ':' in ipaddr:
ipaddr = socket.inet_pton(socket.AF_INET6, ipaddr)
elif '.' in ipaddr:
ipaddr = socket.inet_pton(socket.AF_INET, ipaddr)
with neighlock:
updated = False
if os.times()[4] > (neightime + 30):
_update_neigh()
updated = True
hwaddr = neightable.get(ipaddr, None)
if not hwaddr and not updated:
_update_neigh()
hwaddr = neightable.get(ipaddr, None)
if hwaddr:
hwaddr = ':'.join(['{:02x}'.format(x) for x in bytearray(hwaddr)])
return hwaddr
if __name__ == '__main__':
import sys
print(repr(get_hwaddr(sys.argv[1])))

Some files were not shown because too many files have changed in this diff Show More