Swagg::Blogg

or: How I Learned to Stop Worrying and Love the WWW
Home RSS

So there's these <meta> elements by Meta. Well not the <meta> element itself... But Open Graph. I wanted my links to look like this when I spam them in Discord:

A link preview of an article from web3isgoinggreat.com

Remember that scene from American Psycho where Christian Bale is sweating over a business card?

So anyways, I look at the HTML and see these <meta> tags. That's gotta be it, I mean I see these then some tags that say something about Twitter and I don't want Twitter since I'm really actually not a terrible person once you get to know me so I guess I'll implement these tags. The image seems easy enough, throw it in a Mojo template but what's annoying is I have my <head> entirely in the layout rather than the template itself. I first considered a partial template like I've seen in Rails before but Mojo couldn't find the partial. I think this was due to me just using a single layout for all my templates associated with different controllers. My site is pretty simple so honestly I didn't want to copy the same layout for every controller.

So I was left to hit the docs looking for some sort of feature or something that could help me out here. Finally I settled on the content_for() helper. In my layout I put the following:

<head>
  <!-- Some stuff here... -->
  <%= content 'open_graph' =%>
  <meta property="og:url" content="<%= url_for->to_abs %>">
  <meta property="og:site_name" content="Post::Text">
  <meta property="og:image"
        content="<%= url_for('/images/logo.png')->to_abs %>">
  <meta property="og:image:type" content="image/png">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="1200">
  <meta property="og:image:alt"
        content="Post::Text logo; a small nerdy anime girl giving a V sign">
</head>

Now in my templates I simply do:

% layout 'default';
% title 'New Thread';
% content_for open_graph => begin
  <meta property="og:type" content="website">
  <meta property="og:title" content="<%= title %>">
  <meta property="og:description" content="Start a new thread.">
% end
<h2 class="page-title"><%= title %></h2>
<!-- Begin the rest of the page here... -->

Well that oughta do it! I mean I even made my image 1200 pixels wide, this oughta look breathtaking...

A URL preivew of posttext.pl with a smaller image preview

That's... A URL preview but why is my image so smol? It should be 4K ultra-HD like Molly White's site! I mean yeah there's these Twitter tags but why would it use those? There's not even a 'site name' and I clearly see them in both our previews so it must be using Open Graph. Unless... It's using both. But there's no way it'd do that I mean are the Twitter tags even considered a standard? Hell, is Open Graph even a standard? Out of desperation I try...

% content_for twitter_card => begin
  <meta name="twitter:title" content="<%= title %>">
  <meta name="twitter:description" content="Start a new thread.">
% end

And back to the layout:

<%= content 'twitter_card' =%>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@swaggboi@eattherich.club">
<meta name="twitter:site"
      content="@posttext@fedi.seriousbusiness.international">
<meta name="twitter:image"
      content="<%= url_for('/images/logo.png')->to_abs %>">
<meta name="twitter:image:alt"
      content="Post::Text logo; a small nerdy anime girl giving a V sign">

Well, what's it gunna be Discord? Make my day...

The same link to posttext.pl but now the image is large

😳

Alright then... It's got the site title from Open Graph... And the image from the stupid Twitter tags. I guess. Cool.

Well anyways that's done and Post::Text is finally live so I'm happy. On to the next harebrained project! 🫡

P.S. I know it's been a while since I've blogged, my last prediction is partially becoming true already 💀

#perl #mojolicious #textboard

Anyone got any New Years predictions?

I predict that “human-generated” code will be deprecated as unsafe in favor of AI-generated code. Layoffs and record profits will follow.

I actually normally wouldn't enjoy this but thankfully this Mojo::Pg wrapper makes it easy. I honestly don't have any experience with the original DBD::Pg as I'm still very new to the database world so I hope this doesn't read too much like I'm lost in the sauce. First I took a look at my current tables using the psql tool:

post_text=> \d
                   List of relations
 Schema |         Name          |   Type   |   Owner
--------+-----------------------+----------+-----------
 public | mojo_migrations       | table    | post_text
 public | replies               | table    | post_text
 public | replies_reply_id_seq  | sequence | post_text
 public | threads               | table    | post_text
 public | threads_thread_id_seq | sequence | post_text
(5 rows)

post_text=> \d replies;
                                            Table "public.replies"
     Column     |           Type           | Collation | Nullable |                  Default
----------------+--------------------------+-----------+----------+-------------------------------------------
 reply_id       | integer                  |           | not null | nextval('replies_reply_id_seq'::regclass)
 thread_id      | integer                  |           |          |
 reply_date     | timestamp with time zone |           | not null | now()
 reply_author   | character varying(64)    |           |          |
 reply_body     | character varying(4096)  |           |          |
 hidden_status  | boolean                  |           | not null |
 flagged_status | boolean                  |           | not null |
Indexes:
    "replies_pkey" PRIMARY KEY, btree (reply_id)
Foreign-key constraints:
    "replies_thread_id_fkey" FOREIGN KEY (thread_id) REFERENCES threads(thread_id)

These long lines are ugly so here's a pastebin link. Essentially I need to s/reply_/remark_/g; including that sequence, index and foreign-key constraint:

-- This file is migrations/5/up.sql btw

 ALTER TABLE replies
RENAME TO remarks;

 ALTER TABLE remarks
RENAME reply_id
    TO remark_id;

 ALTER TABLE remarks
RENAME reply_date
    TO remark_date;

 ALTER TABLE remarks
RENAME reply_author
   TO remark_author;

 ALTER TABLE remarks
RENAME reply_body
    TO remark_body;

 ALTER TABLE remarks
RENAME CONSTRAINT replies_thread_id_fkey
    TO remarks_thread_id_fkey;

 ALTER INDEX replies_pkey
RENAME TO remarks_pkey;

 ALTER SEQUENCE replies_reply_id_seq
RENAME TO remarks_remark_id_seq;

I'm also going to do the exact opposite for our rolling-back pleasure:

-- This one is migrations/5/down.sql

 ALTER TABLE remarks
RENAME TO replies;

 ALTER TABLE replies
RENAME remark_id
    TO reply_id;

 ALTER TABLE replies
RENAME remark_date
    TO reply_date;

 ALTER TABLE replies
RENAME remark_author
    TO reply_author;

 ALTER TABLE replies
RENAME remark_body
    TO reply_body;

 ALTER TABLE replies
RENAME CONSTRAINT remarks_thread_id_fkey
    TO replies_thread_id_fkey;

 ALTER INDEX remarks_pkey
RENAME TO replies_pkey;

 ALTER SEQUENCE remarks_remark_id_seq
RENAME TO replies_reply_id_seq;

Idk why but I always find this step feels weird to me because it feels like I'm undoing the undo lol. But we will need it later so let's absolutely include it. Now I'm gunna see if it works...

daniel@netburst:~/git/PostText$ ./PostText.pl eval 'app->pg->migrations->from_dir("migrations")->migrate(5);'
daniel@netburst:~/git/PostText$ echo $?
0

No news is good news I guess. Let's whip out psql again and see how it looks:

post_text=> \d
                   List of relations
 Schema |         Name          |   Type   |   Owner
--------+-----------------------+----------+-----------
 public | mojo_migrations       | table    | post_text
 public | remarks               | table    | post_text
 public | remarks_remark_id_seq | sequence | post_text
 public | threads               | table    | post_text
 public | threads_thread_id_seq | sequence | post_text
(5 rows)

post_text=> \d remarks;
                                            Table "public.remarks"
     Column     |           Type           | Collation | Nullable |                  Default
----------------+--------------------------+-----------+----------+--------------------------------------------
 remark_id      | integer                  |           | not null | nextval('remarks_remark_id_seq'::regclass)
 thread_id      | integer                  |           |          |
 remark_date    | timestamp with time zone |           | not null | now()
 remark_author  | character varying(64)    |           |          |
 remark_body    | character varying(4096)  |           |          |
 hidden_status  | boolean                  |           | not null |
 flagged_status | boolean                  |           | not null |
Indexes:
    "remarks_pkey" PRIMARY KEY, btree (remark_id)
Foreign-key constraints:
    "remarks_thread_id_fkey" FOREIGN KEY (thread_id) REFERENCES threads(thread_id)

Aaaaaand the pastebin. I think we're in good shape. I'm going to migrate back for now as I still need to make some changes in the controller logic to use the new Remark model instead of my old Reply model.

daniel@netburst:~/git/PostText$ ./PostText.pl eval 'app->pg->migrations->from_dir("migrations")->migrate(4);'
daniel@netburst:~/git/PostText$ echo $?
0

I keep the migration hard-coded in the method call in the app itself just to save myself from accidentally migrating to the latest before it's ready:

# From PostText.pl
app->pg->migrations->from_dir('migrations')->migrate(4);

I know there's some reason I started doing that... I accidentally something but now I can't remember. Gunna just stick with the cargo cult and leave it be.

Next I gotta work on the aforementioned controller logic. And then moar tests.

#database #mojolicious #perl #sql #webdev

What should I do...

Is this dangerous?

So I ran into a funny little snafu last night. I finally implemented my Reply model for my little textboard project but once I did this I noticed the app no longer shows in the snazzy built-in error pages. That's bummer because I find them real helpful when running apps in development mode. I kinda gave up on this last night and looked at it again this morning and noticed the following in the console output (requesting a route that does not exist, expect a 404):

[2022-08-20 13:08:43.53717] [27345] [trace] [CgcDACZgwvD4] GET "/swagg"
Mojo::Reactor::Poll: I/O watcher failed: Can't locate object method "exception" via package "PostText::Model::Reply" at /home/daniel/perl5/lib/perl5/Mojolicious.pm line 200.

It must be trying to dereference exception from an object named reply... Let's see...

[daniel@netburst mojo]$ grep -i -r '\->reply' * | grep -i 'lite'
t/mojolicious/exception_lite_app.t:  $c->reply->exception(undef);
t/mojolicious/exception_lite_app.t:  $c->reply->exception;
t/mojolicious/exception_lite_app.t:  $c->reply->exception(Mojo::Exception->new);
t/mojolicious/lite_app.t:  $c->render_maybe('this_does_not_ever_exist') or $c->reply->static($file);
t/mojolicious/lite_app.t:get '/static' => sub { shift->reply->static('hello.txt') };
t/mojolicious/longpolling_lite_app.t:  Mojo::IOLoop->timer(0.25 => sub { $c->reply->static('hello.txt') });
t/mojolicious/static_lite_app.t:get '/hello3.txt' => sub { shift->reply->static('hello2.txt') };
t/mojolicious/static_lite_app.t:  $c->reply->static('hello2.txt');
t/mojolicious/static_lite_app.t:  $c->reply->asset($mem);
t/mojolicious/static_lite_app.t:  $c->reply->file(curfile->sibling('templates2', '42.html.ep'));

Gah! I think I can work around this I suppose by just renaming the reply helper to something other than reply but for consistency I'm going to go ahead and migrate everything to use the name Remark instead of Reply. 'Comment' would remind me too much of social media so I guess these threads are gettin remarked upon.

This, of course, means another night of databases. 😩

#perl #mojolicious #webdev #database

I used to use the flash() helper in Mojolicious for reporting errors back to the user but recently discovered I can just use Mojolicious::Validator::Validation in my templates which seems like the right tool for the job. I was already using it in my Mojolicious::Lite app like so:

$v->required('name' )->size(1, 63  );
$v->required('title')->size(1, 127 );
$v->required('post' )->size(2, 4000);

But other than changing the status to 400 I didn't realize I could use this to also report to the user that their request was invalid. The methods can be used in the templates themselves:

<form method="post">
  <div class="name field">
    <%= label_for name => 'Author' %>
    <%= text_field name =>'Anonymous', maxlength => 63, minlength => 1 %>
    <% if (my $error = validation->error('name')) { =%>
      <p class="field-with-error">Invalid name: 1 to 63 characters please.</p>
    <% } =%>
  </div>
  <div class="title field">
    <%= label_for title => 'Title' %>
    <%= text_field 'title', maxlength => 127, minlength => 1 %>
    <% if (my $error = validation->error('title')) { =%>
      <p class="field-with-error">Invalid title: 1 to 127 characters please.</p>
    <% } =%>
  </div>
  <div class="text field">
    <%= label_for post => 'Text' %>
    <%= text_area 'post', (
        maxlength => 4000,
        minlength => 2,
        required  => 'true',
        rows      => 6
    ) %>
    <% if (my $error = validation->error('post')) { =%>
      <p class="field-with-error">Invalid post: Up to 4,000 characters only.</p>
    <% } =%>
  </div>
  <%= submit_button 'Post', class => 'post button' %>
</form>

So when the user makes their initial GET request, the following HTML is rendered within the form:

<div class="name field">
  <label for="name">Author</label>
  <input maxlength="63" minlength="1" name="name" type="text" value="Anonymous">
</div>
<div class="title field">
  <label for="title">Title</label>
  <input maxlength="127" minlength="1" name="title" type="text">
</div>
<div class="text field">
  <label for="post">Text</label>
  <textarea maxlength="4000" minlength="2" name="post" required="true" rows="6"></textarea>
</div>
<input class="post button" type="submit" value="Post">

Now let's say you screw up and submit a null value for the Title in your subsequent POST request, in your response the form is rendered again like this:

<div class="name field">
  <label for="name">Author</label>
  <input maxlength="63" minlength="1" name="name" type="text" value="anon">
</div>
<div class="title field">
  <label class="field-with-error" for="title">Title</label>
  <input class="field-with-error" maxlength="127" minlength="1" name="title" type="text">
    <p class="field-with-error">Invalid title: 1 to 127 characters please.</p>
</div>
<div class="text field">
  <label for="post">Text</label>
  <textarea maxlength="4000" minlength="2" name="post" required="true" rows="6">hi</textarea>
</div>
<input class="post button" type="submit" value="Post">

The class attribute field-with-error was added to the invalid fields allowing me to decorate this to hint to the user. Then I added another little paragraph (with the same class) to make sure accessibility isn't an issue. These are definitely scenarios where I choose TagHelpers over vanilla HTML in my templates.

Also while testing all of this, I was resorting to using curl to submit invalid input since my browser won't let me thanks to attributes such as maxlength and minlength being present. Finally I realized I could do the same with Mojo's built-in commands which is awesome:

$ ./PostText.pl get -M POST -f 'name=anon' -f 'title=' -f 'post=hi' '/post'
[2022-08-15 19:28:16.84229] [76893] [trace] [jJBy5DsZGrMq] POST "/post"
[2022-08-15 19:28:16.84266] [76893] [trace] [jJBy5DsZGrMq] Routing to a callback
[2022-08-15 19:28:16.84279] [76893] [trace] [jJBy5DsZGrMq] Routing to a callback
[2022-08-15 19:28:16.84359] [76893] [trace] [jJBy5DsZGrMq] Rendering template "post.html.ep"
[2022-08-15 19:28:16.84564] [76893] [trace] [jJBy5DsZGrMq] Rendering template "layouts/main.html.ep"
[2022-08-15 19:28:16.84741] [76893] [trace] [jJBy5DsZGrMq] 400 Bad Request (0.005116s, 195.465/s)
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Post::Text - New Thread</title>
  <link href="/asset/942e7be1d2/PostText.css" rel="stylesheet">
</head>
<body>
<h1>Post::Text</h1>
<nav>
  <a href="/view">View</a>
  <a href="/post">New</a>
</nav>
<hr>
<h2>New Thread</h2>
<form method="post">
  <div class="name field">
    <label for="name">Author</label>
    <input maxlength="63" minlength="1" name="name" type="text" value="anon">
  </div>
  <div class="title field">
    <label class="field-with-error" for="title">Title</label>
    <input class="field-with-error" maxlength="127" minlength="1" name="title" type="text">
      <p class="field-with-error">Invalid title: 1 to 127 characters please.</p>
  </div>
  <div class="text field">
    <label for="post">Text</label>
    <textarea maxlength="4000" minlength="2" name="post" required="true" rows="6">hi</textarea>
  </div>
  <input class="post button" type="submit" value="Post">
</form>
<footer>
  <p>In UTF-8 we trust.</p>
</footer>
</body>
</html>

I see two ways forward on this little project. I can maybe pause now and blow this up into a full-structure Mojolicious app or I can implement my next model (involving new-to-me SQL stuff like FOREIGN KEY and JOIN). Really either of these will be new-to-me and will take some time so I'll probably try to pick the lowest hanging fruit of the two... Once I figure out what the hell that is.

#perl #mojolicious

So I used to read data into an array of arrays like so:

sub get_threads($self) {
    $self->pg->db->query(<<~'END_SQL')->arrays()
        SELECT thread_id,
               TO_CHAR(thread_date, 'Dy Mon DD HH:MI:SS AM TZ YYYY'),
               thread_author,
               thread_title,
               thread_body
          FROM threads
         WHERE NOT hidden_status
         ORDER BY thread_date DESC;
       END_SQL
}

Then I'd plop that data into templates like so:

<% for my $thread (@$threads) { =%>
  <article class="thread">
    <h3 class="title"><%= @$thread[3] %></h3>
    <h4 class="date"><%= @$thread[1] %></h4>
    <h5 class="author"><%= @$thread[2] %></h5>
    <p class="body"><%= @$thread[0] %></p>
  </article>
<% } =%>

This is already pretty cool but I kept losing track of what index number went to which data field. Then I saw that there is a hashes() method in Mojo::Pg::Results and thought... What if we used 100% of the brain? (Edit: Formatting turned out bad for this one)

sub get_threads($self) {
    $self->pg->db->query(<<~'END_SQL')->hashes()
        SELECT thread_id                                             AS id,
               TO_CHAR(thread_date, 'Dy Mon DD HH:MI:SS AM TZ YYYY') AS date,
               thread_author                                         AS author,
               thread_title                                          AS title,
               thread_body                                           AS body
          FROM threads
         WHERE NOT hidden_status
         ORDER BY thread_date DESC;
       END_SQL
}

By using SQL to assign an alias to the column names my templates now look much cleaner:

<% for my $thread (@$threads) { =%>
  <article class="thread">
    <h3 class="title"><%= %$thread{'title'} %></h3>
    <h4 class="date"><%= %$thread{'date'} %></h4>
    <h5 class="author"><%= %$thread{'author'} %></h5>
    <p class="body"><%= %$thread{'body'} %></p>
  </article>
<% } =%>

Readability becomes more and more important as my memory gets worse and worse... I need to learn POD so if I'm a boi of my word there may be a blog post on that in the future. Knowing me... Years in the future.

#perl #mojolicious #sql

I will say if I had to choose either PHP or JS, I'd choose JS lol. But I do think the World Wide Web without Perl is worse off. I hope the O.G. Perl Mojolicious lives on in harmony.

#perl #javascript #mojolicious #insomnia

I recently have come to the come to the conclusion that 'warranty' as we know it in 2022 is a big fat Greek lie because:

  1. Devices aren't even repairable anymore. What are they gunna do for you?
  2. Who says they'll honor the warranty anyways?

I'm swearing them off forever now. My new warranty is the SwaggPlan:

  1. Always decline the warranty. Always.
  2. Use search tools like StackOverflow, YouTube, etc for fucking everything. Just this week I've fixed a fridge and HVAC. I know literally nothing about these things.
  3. You are probably capable of figuring out much more shit than you give yourself credit for!

Forget 'peace of mind', warranty only provides the illusion of that. Real peace of mind is ensuring you're never reliant on people/processes that are demonstrably unreliable.

Right to repair now,

Dan

#righttorepair #diy

There exists in-depth documentation into how SELinux works. There's even more tutorials that begin with “Step one: disable SELinux.” This is neither. I wanted to bang out a quick write-up on how I “cope” with SELinux rather than either dedicating the rest of my life into exploring the depths of it or saying fuck it and turning it off. I think my approach is better than just disabling it and is generally Good Enough™. I won't pretend to explain the mechanics of SELinux as there's not enough caffeine on the planet for me to feign that level of understanding but I do hope this will encourage more mere mortals like myself to try leaving it on and feel the fuzzy warmth of the security blanket that is SELinux.

Modes

There's 3 modes to SELinux: disabled (wrong!), permissive and enforcing. The first thing I do to a freshly installed Red Hat(-like) system is put it into permissive mode:

# ruby -i.bak -pe 'sub(/^(SELINUX=)[a-z]+$/, %q{\1permissive})' /etc/selinux/config
# setenforce 0
# getenforce 
Permissive

The Ruby one-liner there will set up SELinux to boot into permissive mode. The setenforce command next will immediately set it to permissive (no reboot required). Finally, getenforce should return “Permissive” to confirm that we're in permissive mode. What the hell is permissive mode then? Think of it as an “alert only” or “fail open” mode. SELinux is still running and will log violations as expected but will not actually intervene to prevent the violation from occurring. What I like about this is we can take our new system for a spin for a couple days and log the violations we'll inevitably trigger. This gives us the logs we need to generate new policies to exempt these things without going through the painful process of getting denied, generating a single policy to get around the denial, ah shit now there's a new denial, rinse and repeat.

Finally there's disabled and enforcing mode. Enforcing is full-blown, “fail closed” SELinux. Disabled is what you think it is: SELinux is completely disabled. Switching to/from disabled will cost you a reboot but you shouldn't be doing that anyways you BOFH!

Labels and Types

In the spirit of doing things Good Enough™ I'm not going to tell you how to write your own policies or even get into how exactly policies work. Just know that SELinux policies reference file and/or process labels to determine what is or isn't allowed to happen. You can typically expose these labels by adding the -Z flag to many of the existing tools you're familiar with:

# ls -Z /etc/selinux/config
system_u:object_r:selinux_config_t:s0 /etc/selinux/config

The important field on this label is that second-to-last one, the “type” field which is denoted with the “_t” suffix. That's all I have to say on labels, let's talk about the tools that essentially handle all this for us.

Our Tools

On a RHEL-ish system you'll want to install the audit and policycoreutils-python-utils packages if you don't have them already. With my system in permissive mode and having run my favorite programs and poked at my files, I typically have some violations logged. We can check it out like so:

# ausearch -m avc -ts boot
----
time->Sat Oct 16 13:41:00 2021
type=AVC msg=audit(1634406060.965:325): avc:  denied  { watch } for  pid=2799 comm="dbus-daemon" path="/var/lib/snapd/dbus-1/services" dev="nvme0n1p4" ino=3792304 scontext=system_u:system_r:xdm_t:s0-s0:c0.c1023 tcontext=system_u:object_r:snappy_var_lib_t:s0 tclass=dir permissive=1

Yep, looks like snapd tried to do a thing that SELinux disagreed with. And yeah you can use that -ts flag with different values but typically I just go with “boot” which goes back to the system's last boot. Let's generate a policy to allow this:

# ausearch -m avc -ts boot | audit2allow -M snapd20211016
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i snapd20211016.pp

Now before we run that semodule command it wants us to run I like to look at something first. You see this tool actually produced two files for us. There's snapd20211016.pp but there's also this snapd20211016.te:

# file snapd20211016.??
snapd20211016.pp: SE Linux modular policy version 1, 1 sections, mod version 20, MLS, module name snapd20211016\003
snapd20211016.te: ASCII text

Think of the .te file as the plain-text “source code” for the policy while the .pp file is that “source code” in its compiled, ready-to-rock form. I always first peek at the plain-text version to see if this is something I'm ready to install:

# cat snapd20211016.te

module snapd20211016 1.0;

require {
	type snappy_var_lib_t;
	type xdm_t;
	class dir watch;
}

#============= xdm_t ==============
allow xdm_t snappy_var_lib_t:dir watch;

This looks fine to me so I'd go ahead and install it like so (just like we were told by audit2allow):

# semodule -i snapd20211016.pp

Under what circumstances would I not go ahead and install the policy? One of two scenarios:

  1. The name I specified with audit2allow -M isn't accurate. If you pipe multiple alerts specifically you might have a mix of unrelated violations. In the spirit of Good Enough™ I typically just change the name to something like local20211016 and make myself a big ol' polka policy.
  2. The policy contains a comment telling me I can use some boolean to allow this. This means the policy doesn't need to be installed at all.

What's an SELinux boolean? They're additional SELinux controls that exist outside of the whole policies/labels/types dance. If you don't want to bother with this then that's fine; install your policy and be at peace. Personally I find it easier in the long run to just flip the boolean however as it means you'll likely never see violations like this ever again, so I'll say a few things on booleans for those who've made it this far into the blog post with your sanity in tact.

Booleans

Here's one I see every time I fire up Counter-Strike 1.6:

# cat hl_linux10032021.te

module hl_linux10032021 1.0;

require {
	type unconfined_t;
	class process execheap;
}

#============= unconfined_t ==============

#!!!! This avc can be allowed using the boolean 'selinuxuser_execheap'
allow unconfined_t self:process execheap;

See that comment there that audit2allow was nice enough to exclaim several times for me? Whatever execheap is, I love it and I want some more of it. Let's not worry about installing this policy and instead flip the boolean to enable it for good:

# semanage boolean --modify --on selinuxuser_execheap

I don't think we'll ever be seeing that violation ever again.

Let's Get This Over With

Alright once we've got our policies installed and/or booleans flipped, let's complete our journey by going back into enforcing mode:

# cd /etc/selinux/
# cp config.bak config
# setenforce 1

Some Thoughts on SELinux Administration

I figured I'd just share some observations I've made after coping with SELinux for the past several years.

Typically I'm a huge believer in the RTFM philosophy but the man pages for tools like semanage are simply bad. Almost none of the command-line options are documented here. Oddly enough I have better luck using the bash completion to see what options are available but this isn't always available depending on your shell environment and even still there's plenty of command-line options missing. Best bet if you want to explore the tools more would be to create yourself a free Red Hat developer account to be granted full access to their online documentation. There's also a book written by the SELinux maintainer for Gentoo. There's a third edition out now as of 2020 but I couldn't find it anywhere except Amazon at the time I'm writing this. This could be an interesting read but if you get this far I think you're beyond Good Enough™ and probably know way too much to be bothered with this post.

Finally it's probably a good idea to use ausearch every now and then to check for any new violations. Particularly after installing/running a new program or performing a system upgrade. Speaking of system upgrade, I like to go ahead and just throw it back into permissive mode before doing stuff like this. Just keeps those potential violations out of our way while we run all kinds of stuff and poke all kinds of things in the process. Once in a blue moon I'll be working on some fun things and get opaque errors. Sometimes it'll say permission denied even though the file permissions look fine. Sometimes it'll be much more opaque. Sometimes I won't even get any apparent error message. Before you pull your hair out troubleshooting every damn thing, remember to check ausearch! I tend to forget SELinux exists on my servers where I don't have to think about it so often as things don't change so frequently on my servers and I end up troubleshooting everything but SELinux.

Don't be afraid to revert to permissive mode for a bit if you find yourself flabbergasted and need to come back to it later to get things sorted and back into enforcing mode.

Godspeed,

Dan

#fedora #redhat #rhel #selinux

I've put forth some effort recently towards exploring some other Perl-ish but not-Perl scripting languages to (I hope) reinforce my resume. I love Perl but if I hope to become a real professional with my programming then I think it behooves me to branch out a bit. I haven't strayed far from Perl however as the languages that have intrigued me the most so far have been Raku and Javascript.

What is or isn't “Perl-ish” I suppose is subjective but I think these two have a syntax I can find some familiarity with. I still haven't felt empowered enough yet to really want to use either of these for actual $workStuff yet. Oddly enough I've reached for another Perl-ish language for this that I initially only cared about for its killer app.

If there's one thing that kinda struck me as odd about Ruby it's how variables are “defined.” Honestly I have a tendancy to spend hours of trial an error when learning new things to spare me the ten minutes it takes to just read the documentation but I got to thinking about it and realized I had a knowledge gap in what makes a variable “defined”? If I declare a variable like so:

$ re.pl
$ my $string = 'ayy... lmao';

I've declared and defined the variable. But there's this other thing I've been doing without ever giving it much thought:

$ my ($string, %hash, @list);

So I just defined 3 different variables at once right? Turns out that is not the case as I learned in, of all things, a JS tutorial. I've declared these three variables but they are infact, undefined:

$ node
Welcome to Node.js v14.17.0.
Type ".help" for more information.
> let string, hash, list;
undefined
> string
undefined
> undeclaredVar
Uncaught ReferenceError: undeclaredVar is not defined

I recognized this as undef in Perl:

$ re.pl
$ my ($string, %hash, @list);
$ use Data::Dumper;
$ Dumper($string);
$VAR1 = undef;

Cool. So there's declared variables and then there's defined variables. But the thing about Ruby is there's no let or my keywords. The way you define a variable in Ruby reminds me more of bash:

$ irb
irb(main):001:0> string = 'ayy... lmao'
=> "ayy... lmao"

But what if I just want to “declare” a variable but not “define” it? The best solution I've found so far goes like this:

irb(main):001:0> string = nil
=> nil

This is actually different than:

irb(main):001:0> string = ''
=> ""

Coincidentally (or perhaps not, I'm using a filler word) only false and nil will evaluate as false in Ruby:

irb(main):001:0> string = ''
=> ""
irb(main):002:0> puts "ayy... lmao" if string
ayy... lmao
=> nil

That means this madness is real:

irb(main):003:0> puts "ayy... lmao" if 0
ayy... lmao
=> nil

If only Ruby had let or my so I could declare but not define variables... Wait. What if I did something like... ?:

irb(main):001:0> string, hash, list = nil
=> nil
irb(main):002:0> puts "ayy... lmao" if list
=> nil
irb(main):003:0> puts "ayy... lmao" if undeclaredVar
(irb):3:in `<main>': undefined local variable or method `undeclaredVar' for main:Object (NameError)
	from /usr/share/gems/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
	from /usr/bin/irb:23:in `load'
	from /usr/bin/irb:23:in `<main>'

Ultimately it seems Ruby gives me all the roap I need to hang myself. It just doesn't look right to me because I've grown fond of let and/or my. One could write their entire web backend and frontend both in JS but I'd rather use Ruby on Rails in my backend. Or in theory at least. I learned Ruby to learn Rails but I can't yet write anything useful with Rails. But with plain Ruby I can do some neat system-side stuff as I would with Perl. My homepage uses Perl on the backend because I already knew how to do it with Perl. And my frontend is more JS than CSS because I actually like JS the more I learn about it.

But I really need to stop dicking around and learn Rails already. I wrote a blog post for the first time in months to put off reading another chapter of my Rails book. On the brightside I won't have to learn SQL if I can master ActiveRecord. How nice would it be to just do everything in one language? SQL (pronounced like sequel) must be a conspiracy to sell more CS degrees. Or Oracle licenses.

I should probably just learn SQL so I can finally learn Rails.

Dan B

#perl #raku #javascript #ruby

Enter your email to subscribe to updates.