Linux 3.0 was a big deal in most peoples’ eyes. For as long as I can remember using Linux (since 2004) the kernel was at 2.y.z. 3.0 was only released either last or this year, even.
Then Linus made the announcement that 3.0 wasn’t going to be anything more than a usual patch fix (i.e.: no new features to write home about). His jsutification for this is “why not?” Its his product, fine. Not much anyone can do about that after all.
Now there’s talk of bringing out 4.0. Again, nothing but a bunch of bug fixes.
While it’ll be nice for a short while to see that 4.0.z on my screen, I feel the “its my stuff so you can only look at it” mentality is running its course. Nothing new is being done with the kernel, why keep changing hte major version number?
At this point just make the major version 42 and make all the geeks squirm with happiness. Its just as effective now as actual versioning reasons where in the past.
Back when I was going to ITT for my associates everyone stressed the importance of certifications. When looking at the job market it made sense, too. Most entry-level positions were asking for A+ or Net+, even some wanted CCNA (…no.)
Looking at the job market 5-ish years later a lot has apparently changed. Very little postings are even asking for certifications, let alone even education. While my belief is that a masters will be the bare minimum in the next 5-10 years, I wouldn’t be surprised to be completely wrong on that now.
Back to the question at hand, though. A straight forward answer would be “depends.” But, that doesn’t do you any good now, does it?
Best thing to do is review jobs in your area and see what various places are asking for. There’s some that will demand you have certifications and others just care if you know what you’re doing. It will give you a good idea what it’ll take to get a job where you want to work and if you’re lucky ask them at a job fair if they are there.
My personal belief is that they are pointless, though. At this point the same as getting a bachelors, you can’t prove your worth by a piece of paper.
My ideal vision of when I build my business up high enough to be able to hire people is this simple process: give them a lab (probably through a VM so you can snapshot and restore). They have various issuees they need to solve. Basically keylog what they do (modifying the shell history is too easy) and determine from there. You’ll have a good idea of what they do and how they solve problems that are directed toward your business.
Simple? Probably too simple. However, doing the job is what’s important, how they get the solution is only part of the issue.
I’m not sure how the job market is everywhere else, but near Detroit where I live it is like trying to find a horse in a barn. There’s jobs everywhere but everyone hiring is either a recruiter or nit picking on the details it seems.
Don’t get me wrong, recruiters can be the best tool anyone can have in finding a job. The problem is that most don’t know IT terms or how they relate. Case in point: Linux.
I don’t have much experience with Red Hat (sans back in the day when I got it from school plus Fedora and CentOS). Most people seem to dismiss me right away for that fact alone. What they fail to realize really is that most skills from one distro to another is transferable.
Every Linux system is going to have an init system, kernel, package manager, etc… heck, even man pages (if a system doesn’t have man pages then something is seriously wrong).
I don’t understand the justification behind telling a client they’re not worthy of a position just because they don’t have experience with one or two distro’s, even after explaining how what you know so far is transferable or at the very least adaptable.
I love Linux but it seems the one thing that hurts it the most for those who aren’t senior-level position people is the vast abundance of flavors out there. It hurts us more than helps when we dedicate so much time in learning just how to work Linux in general to be thrown out because we used 10.04 instead of 10.10 of Ubuntu (not real life example but feeling is still there).
You can ask basically anyone who manages multiple Linux (and even Windows) servers what they use to monitor their systems and its a high possibility that they’ll say “Nagios” or a variant of it (i.e.: OpsView, Icinga, Centreon, etc…). There’s no doubt it has a strong hold in the market and there are plenty of positives to it, but is it for everyone?
Nagios requires the core itself installed on the main/master server as well as a daemon installed on every server to be monitored. The core then parses config files and performs checks to make sure stats are correct. While this isn’t painstaking the process of installing both can be troublesome, especially if you’re doing it by hand (not using an auto-installer script).
Editing the configuration of Nagios (core or daemon) is a bit of a challenge. While there is documentation, it would seem like trying to solve a rubic cube would be simpler until you really understand whats going on. I can see it being very beneficial when Nagios was first starting out but now with it branching out so much into such a more sophisticated piece of software the configuration is convoluted.
Luckily Nagios is pretty much a set it and forget solution (unless you want to add more plugins to it).
This is my biggest gripe with Nagios personally: there’s virtually no customization. The program itself is a compiled Perl/CGI script. About the only thing you can customize are the plugins for monitoring. While I can see some points for making it closed-source, given some of the obscure warnings it can spit out I think opening it up even a little bit would be far more beneficial.
This is a short list, but Nagios does what its supposed to do and doesn’t offer much fluff. The web UI is pretty horrid (looks like its from 1990) but it presents information you need. The alert system is nice but could be easier to work with, and while there are numerous frontend wrappers for it, they all still require Nagios itself.
The biggest compliment I can give it in the end is that it uses perf data to return information about a plugin, which is pretty universal as to how its formatted.
Again an easy but essential requirement for our two-factor system. This will be another Flask web route and mostly database driven. Lets look at the flow of how things will transpire first for this project:
SMS: POST number to /sms -> URI generates token and sends to number via SMS -> User enters their number and token to website and submits -> Site POSTs number and token to /auth/#/token -> HTTP 200 for authenticated, 403 for failure
Voice: POST number to /who -> URI generates token and sends to number via call -> User enters their number and token to website and submits -> Site POSTs number and token to /auth/#/token -> HTTP 200 for authenticated, 403 for failure
The only difference between the two is how the user receives their token. We’ll use that to our advantage. Here is the auth URI:
@app.route("/auth//", methods=['GET','POST']) def auth(number, token): valid = False up = phone.select(phone.id).where(phone.digits==number).get() records = SelectQuery(tokens).where((tokens.token==token) & (tokens.phone==up.id)).count() if records: valid = True if valid: tokens.update(token="").where(tokens.phone==up.id).execute() return make_response("", 200) return make_response("", 403)
If you think its pretty simple that’s because it is. We get the phone ID by looking up the numbers and then check to see if there’s a token ready for the phone number (phone ID has to match as well as token). If the authentication is valid we set the token to “” so no one can use it again for that number (this is one reason why the generate_token method is flawed…its too easy to figure out), and return HTTP/200 (OK) to the user. Otherwise, we return HTTP/403 (Forbidden).
We can definitely make this more intricate, however, and I’ll showcase some of that next time. But this is a good start for anyone wanting to make their own two-factor authentication system.
We’re almost there! Now we need to save the tokens we’ve generated as well as the phone number requesting it. While can be done anywhere I chose to plop it into the generate_token() method because we’d have to write the code twice otherwise. Luckily its a small fix, and we’ll finally be able to use our database stuff now.
Just before the “return token” line in the method add these lines:
try: up = phone.select().where(phone.digits==number).get() except: up = phone.create(digits=number) records = SelectQuery(tokens).where(tokens.phone==up.id).count() if records: tokens.update(token=token).where(tokens.phone==up.id).execute() else: tokens.create(token=token,phone=up.id)
The first try/except block attempts to get the phone information from the database and creates a record of it instead if nothing exists. We then try to update the token for the phone number and if that doesn’t work then we create a new record of it. Very simple and easy but is also vital to our service.
Every incoming and outgoing request to the number (voice and SMS) goes against your balance. Unfortunately there’s nothing you can do to stop people from trying to spam your SMS inbox. There is a silver lining though with voice calls.
On your Twilio dashboard click “Numbers” near the top, then click on your number. Here you’ll be presented with some options. The “Voice Request URL” is what we’re interested in. Remember our /voice URI? We’ll use that to make things fun. So change the URL to http://ipaddress/voice and change the method from POST to GET. Save and leave it be for a bit, we don’t need to change anything else there.
Now we will make a change to our voice method, which also extends what we learned while making outgoing calls. We will be using TwiML again. Replace your @app.route() to the end of the method with this:
@app.route("/voice") def voice(): resp = twiml.Response() resp.reject("rejected") return make_response(str(resp), 200)
Twilio has two methods of rejecting someone: “rejected” and “busy”. When reject() is set to “busy” a busy tone will be played, whereas when “rejected” is used a “this number is not in service” type of message is played.
Now, for the caveat. If resp.reject() is not called first in the line of creating TwiML, your account will be hit with usage. The only way to make it so when incoming calls don’t affect your usage is to call reject() first before any other. However, after reject() is handled via Twilio anything after that is ignored as well. Something else to keep in mind.
Save and now try to call your number. See what happens.
Now we’ll go into making outbound calls. This is pretty similar to SMS but does get more advanced pretty quickly. Now is the time where being Internet-reachable is a necessity.
First, how to create a call. For this I’m going to make the functions easy again:
@app.route("/voice/") def voice(number): try: client.calls.create(url="http://ip_address:5000/who/%d" % number, to="+1%d" % number, from_=TWILIO_NUMBER) return "Call sent." except TwilioRestException, e: return "Unable to send. Reason: %s" % (e.msg)
This is very similar to our SMS method with the exception being “client.calls.create” instead of “client.sms.messages.create”. Also, instead of passing a message to the user we are passing a URL to forward them to. While you don’t have to pass a URL, there does have to be something for Twilio to process (which will be covered shortly). So, basically, when the call is created Twilio will read special XML generated at /who/[number] and go from there (there’s a few options, we’ll cover the basic message reading first). Here’s the method for /who:
@app.route("/who/", methods=['POST']) def who(number): token = generate_token(number) xml = TwiML.Response() xml.say("Your token is: %s" % ". ".join([i for i in token])) return make_response(str(xml), 200)
Twilio only POSTs when itself is referencing a URI/URL, so that is why we only support POST for /who. I won’t go into the basics of TwiML (Twilio’s XML), but basically what’s going on is we are creating a reference to our response XML, and then passing it what we want it to read back to the user. From my experience, if HTTP response is not 200 then you will run into issues, so that is why we force a 200 (HTTP OK) response regardless.
The reason why we rejoin our token like we do is that if we gave it as one long strong then Twilio’s text-to-speech service would jumble it all up. If you just space the characters apart it reads still a little too fast. So we instead make Twilio read each character as if they are a sentence by themselves.
Visit http://ip_address/voice/verified_number (replacing “verified_number” with your verified number like with SMS) and give it a try!
This will be a small blurb compared to the rest but this is rather essential. This isn’t the best way to generate tokens and in no way do I recommend you use this in a practical case. Here is the code, however (place it just below the app = … line):
def generate_token(number): from time import time charlist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+/=" num = str(number+time()).split(".") info = [num[i:i+2] for i in range(0, len(num), 2)] token = "" for i in info: index = int(i)+int(i) token = "%s%s" % (token, str(charlist[index])) return token
Basically what this does is add the current time to the number provided (and since time() returns a decimal we only want the whole number). Then we split the number into multiple pairs and add both digits in the pairs, and that is our place in the token list/charlist. It’s pretty simplistic but it does our job just fine.
When I wrote the original code I made things way too complicated for it. So, I’ll steer you in the direction of NOT doing that, and we’ll make this simple!
After our import lines, add these:
TWILIO_SID = "xxx" TWILIO_AUTH = "yyy" TWILIO_NUMBER = "zzz"
The xxx and yyy will be replaced by whatever values you noted down/get from the Twilio dashboard. You also need to set your Twilio number as well so the service will know who to set the “From” field to.
Now we need to create a reference to Twilio’s API, which also requires us to send the SID and AUTH values:
client = TwilioRestClient(TWILIO_SID, TWILIO_AUTH)
We’ll work on SMS first since its cheaper and easier to work with. First what we need to do is create a route for SMS:
@app.route("/sms", methods=['GET', 'POST']) def sms(): try: client.sms.messages.create(body="Huzzah!", to="+1xxxyyyzzzz", from_=TWILIO_NUMBER) return "SMS sent." except TwilioRestException, e: return "Unable to send. Reason: %s" % (e.msg)
For sake of this project right now replace the hard-coded number “+1xxxyyyzzzz” with the number you signed up for with Twilio (demo accounts can’t send to just any number, paid accounts can) and visit /sms to receive a text message. Pretty cool, huh?! Lets change this a little bit and make it so when a GET request is made to the page it’s sent to a number specified. Here’s the new code block:
@app.route("/sms/") def sms(number): try: client.sms.messages.create(body="Huzzah!", to="+1%d" % number, from_=TWILIO_NUMBER) return "SMS sent." except TwilioRestException, e: return "Unable to send. Reason: %s" % (e.msg)
We made some changes here. First, we removed the ‘methods’ argument to app.route. This was done because we want to just focus on handling GET requests right now, which Flask defaults to when methods is not passed. Next is the route. Now we need to visit /sms/10-digit-number-here to make it work (replacing the “10-digit-number-here” part with the verified number). We still need to pass that info to the method itself so we can use it, and also modify the ‘to’ field so it still sends properly. Now, try again and it should still work!