Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.28] CCT-69: Anonymous registration #3383

Draft
wants to merge 19 commits into
base: subscription-manager-1.28
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 6 additions & 56 deletions src/daemons/rhsmcertd.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ typedef enum {
#define N_(x) x
#define CONFIG_KEY_NOT_FOUND (0)

#define MAX_AUTO_REGISTER_ATTEMPTS 3

#if !GLIB_CHECK_VERSION(2, 58, 0)
#define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void)) (f))
Expand Down Expand Up @@ -97,7 +96,6 @@ static gint arg_reg_interval_minutes = -1;
static gboolean arg_no_splay = FALSE;
static gboolean arg_auto_registration = FALSE;
static int fd_lock = -1;
static int auto_register_attempt = 0;

struct CertCheckData {
int interval_seconds;
Expand Down Expand Up @@ -423,34 +421,19 @@ auto_register(gpointer data)
exit (EXIT_FAILURE);
}
if (pid == 0) {
if (auto_register_attempt < MAX_AUTO_REGISTER_ATTEMPTS) {
debug ("(Auto-registration) executing: %s --auto-register", WORKER);
execl (WORKER, WORKER_NAME, "--auto-register", NULL);
} else {
warn ("(Auto-registration) the number of attempts reached the max limit: %d", MAX_AUTO_REGISTER_ATTEMPTS);
// Return False to not repeat this again
return false;
}
debug ("(Auto-registration) executing: %s --auto-register", WORKER);
execl (WORKER, WORKER_NAME, "--auto-register", NULL);
}

waitpid (pid, &status, 0);
status = WEXITSTATUS (status);

if (status == 0) {
info ("(Auto-registration) performed successfully.");
// No need to repeat this action again
return false;
} else {
auto_register_attempt++;
if (auto_register_attempt < MAX_AUTO_REGISTER_ATTEMPTS) {
warn ("(Auto-registration) failed (%d), retry will occur on next run.", status);
} else {
warn ("(Auto-registration) failed (%d), the number of attempts reached the max limit: %d",
status, MAX_AUTO_REGISTER_ATTEMPTS);
// Return False to not repeat this again
return false;
}
return true;
warn ("(Auto-registration) failed (%d)", status);
return false;
}
}

Expand Down Expand Up @@ -492,30 +475,6 @@ cert_check (gboolean heal)
return TRUE;
}


static gboolean
initial_auto_register (gpointer data)
{
struct CertCheckData *cert_data = data;
gboolean repeat_attempts;

repeat_attempts = auto_register(cert_data);

// When first attempt was not successful, then try to do other
// auto-registration attempts
if (repeat_attempts == true) {
// Add the timeout to begin waiting on interval but offset by the initial
// delay.
g_timeout_add(cert_data->interval_seconds * 1000,
(GSourceFunc) auto_register, cert_data);
// Update timestamp
log_update(cert_data->interval_seconds, cert_data->next_update_file);
}
// Return false so that the timer does
// not run this again.
return false;
}

static gboolean
initial_cert_check (gpointer data)
{
Expand Down Expand Up @@ -919,13 +878,11 @@ main (int argc, char *argv[])
// NOTE: We put the initial checks on a timer so that in the case of systemd,
// we can ensure that the network interfaces are all up before the initial
// checks are done.
int auto_reg_initial_delay = 0;
int auto_attach_initial_delay = 0;
int cert_check_initial_delay = 0;
if (run_now) {
info ("Initial checks will be run now!");
} else {
int auto_reg_offset = 0;
int auto_attach_offset = 0;
int cert_check_offset = 0;
if (splay_enabled == true) {
Expand Down Expand Up @@ -964,16 +921,10 @@ main (int argc, char *argv[])
}
#endif
srand((unsigned int) seed);
auto_reg_offset = gen_random(auto_reg_interval_seconds);
auto_attach_offset = gen_random(heal_interval_seconds);
cert_check_offset = gen_random(cert_interval_seconds);
}

if (auto_reg_enabled) {
auto_reg_initial_delay = INITIAL_DELAY_SECONDS + auto_reg_offset;
info ("Waiting %.1f minutes plus %d splay seconds [%d seconds total] before performing first auto-register",
INITIAL_DELAY_SECONDS / 60.0, auto_reg_offset, auto_reg_initial_delay);
}
auto_attach_initial_delay = INITIAL_DELAY_SECONDS + auto_attach_offset;
info ("Waiting %.1f minutes plus %d splay seconds [%d seconds total] before performing first auto-attach.",
INITIAL_DELAY_SECONDS / 60.0, auto_attach_offset, auto_attach_initial_delay);
Expand All @@ -998,8 +949,7 @@ main (int argc, char *argv[])
auto_attach_data.next_update_file = NEXT_AUTO_ATTACH_UPDATE_FILE;

if (auto_reg_enabled) {
g_timeout_add(auto_reg_initial_delay * 1000,
(GSourceFunc) initial_auto_register, (gpointer) &auto_register_data);
auto_register((gpointer) &auto_register_data);
}
g_timeout_add (cert_check_initial_delay * 1000,
(GSourceFunc) initial_cert_check, (gpointer) &cert_check_data);
Expand All @@ -1010,7 +960,7 @@ main (int argc, char *argv[])
// time. This works for most users, since the cert_interval aligns with
// runs of heal_interval (i.e., heal_interval % cert_interval = 0)
if (auto_reg_enabled) {
log_update (auto_reg_initial_delay, NEXT_AUTO_REGISTER_UPDATE_FILE);
log_update (0, NEXT_AUTO_REGISTER_UPDATE_FILE);
}
log_update (cert_check_initial_delay, NEXT_CERT_UPDATE_FILE);
log_update (auto_attach_initial_delay, NEXT_AUTO_ATTACH_UPDATE_FILE);
Expand Down
22 changes: 16 additions & 6 deletions src/dnf-plugins/subscription-manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
else:
from ConfigParser import ConfigParser

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from subscription_manager.certdirectory import EntitlementDirectory
from subscription_manager.identity import Identity


expired_warning = _("""
*** WARNING ***
The subscription for following product(s) has expired:
Expand Down Expand Up @@ -124,12 +131,15 @@ def _update(cache_only):
Update entitlement certificates and redhat.repo
:param cache_only: is True, when rhsm.full_refresh_on_yum is set to 0 in rhsm.conf
"""

logger.info(_('Updating Subscription Management repositories.'))

identity = inj.require(inj.IDENTITY)

if not identity.is_valid():
logger.info(_("Updating Subscription Management repositories."))
identity: Identity = inj.require(inj.IDENTITY)
ent_dir: EntitlementDirectory = inj.require(inj.ENT_DIR)

# During first phase of anonymous cloud registration the system has
# valid entitlement certificates, but does not yet have any identity.
# We have access to the content, so we shouldn't be reporting missing
# identity certificate.
if not identity.is_valid() and len(ent_dir.list_valid()) == 0:
logger.info(_("Unable to read consumer identity"))

if config.in_container():
Expand Down
72 changes: 43 additions & 29 deletions src/rhsm/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import sys
import time
import traceback
from typing import Optional
from typing import Any, Dict, List, Optional
from pathlib import Path

from email.utils import format_datetime
Expand Down Expand Up @@ -453,7 +453,11 @@ class RateLimitExceededException(RestlibException):
def __init__(self, code, msg=None, headers=None):
super(RateLimitExceededException, self).__init__(code, msg)
self.headers = headers or {}
self.retry_after = safe_int(self.headers.get('retry-after'))
self.retry_after = None
for header, value in self.headers.items():
if header.lower() == "retry-after":
self.retry_after = safe_int(value)
break
self.msg = msg or "Access rate limit exceeded"
if self.retry_after is not None:
self.msg += ", retry access after: %s seconds." % self.retry_after
Expand Down Expand Up @@ -1107,11 +1111,12 @@ def get_supported_resources(self):

return self.resources

def supports_resource(self, resource_name):
"""
Check if the server we're connecting too supports a particular
resource. For our use cases this is generally the plural form
of the resource.
def supports_resource(self, resource_name: Optional[str]):
"""Check if the server supports a particular resource.

:param resource_name:
Resource to be requested.
When `None`, API call `GET /` is made to cache all supported resources.
"""
if self.resources is None:
self._load_supported_resources()
Expand Down Expand Up @@ -1151,32 +1156,28 @@ def shutDown(self):
def ping(self, username=None, password=None):
return self.conn.request_get("/status/")

def getJWToken(self, cloud_id, metadata, signature):
"""
When automatic registration is enabled in rhsm.conf and it was possible
to gather cloud metadata, then it is possible to try to get JSON Web Token
for automatic registration. When candlepin does not provide automatic
registration, then raise exception.
:param cloud_id: ID of cloud provider, e.g. "aws", "azure", "gcp"
:param metadata: string with base64 encoded metadata
:param signature: string with base64 encoded signature
:return: string with JWT
def getCloudJWT(self, cloud_id: str, metadata: str, signature: str) -> Dict[str, Any]:
"""Obtain cloud JWT.

This method is part of the Cloud registration v2: standard or anonymous flow.

:param cloud_id: Cloud provider, e.g. 'aws', 'azure' or 'gcp'.
:param metadata: Base64 encoded public cloud metadata.
:param signature: Base64 encoded public cloud signature.
"""
params = {
data = {
"type": cloud_id,
"metadata": metadata,
"signature": signature
"signature": signature,
}
# "Accept" http header has to be text/plain, because candlepin return
# token as simple text and it is not wrapped in json document
headers = {
"Content-type": "application/json",
"Accept": "text/plain"
"Content-Type": "application/json",
}

return self.conn.request_post(
method="/cloud/authorize",
params=params,
headers=headers
method="/cloud/authorize?version=2",
params=data,
headers=headers,
)

def registerConsumer(self, name="unknown", type="system", facts={},
Expand Down Expand Up @@ -1513,16 +1514,29 @@ def unregisterConsumer(self, consumerId):
method = '/consumers/%s' % self.sanitize(consumerId)
return self.conn.request_delete(method)

def getCertificates(self, consumer_uuid, serials=[]):
def getCertificates(
self,
consumer_uuid: str,
serials: Optional[list] = None,
jwt: Optional[str] = None,
) -> List[dict]:
"""
Fetch all entitlement certificates for this consumer.
Specify a list of serial numbers to filter if desired.

:param consumer_uuid: consumer UUID
:param serials: list of entitlement serial numbers
:param jwt: JWT identifying an anonymous system
"""
method = '/consumers/%s/certificates' % (self.sanitize(consumer_uuid))
if len(serials) > 0:
if serials:
serials_str = ','.join(serials)
method = "%s?serials=%s" % (method, serials_str)
return self.conn.request_get(method)
headers = {}
if jwt:
headers["Authorization"] = "Bearer {jwt}".format(jwt=jwt)

return self.conn.request_get(method, headers=headers)

def getCertificateSerials(self, consumerId):
"""
Expand Down
Loading
Loading