You've written:
getBoard :: String
That doesn't say that getBoard is a function or procedure that returns a String. Rather, it says that getBoard is a String; I mean some specific String like "Hello world" or "foo", for example. Obviously, that's wrong. Rather, getBoard is a procedure you can perform that produces a String as its result. In Haskell, the way you say that is:
getBoard :: IO String
So that's the right answer. If that answer doesn't work for you, please provide more context so we can understand why.
When you say "Note: The getBoard function MUST return a String", that's almost exactly what getBoard :: IO String means: getBoard is a procedure that returns a String. (One subtle point here is that I've said "procedure" instead of "function". That's because in Haskell, "function" means something different from what you're probably expecting from other languages. A function is something that takes parameters. Because getBoard doesn't take parameters, it's not a function, but it is a procedure.)
Did you mean that you want it to be a String, rather than return one? This doesn't make much sense, though. Which specific String would you like it to be?
Okay, now is where I reluctantly admit that there is one way to tell Haskell (incorrectly) that getBoard is a String. You shouldn't do this unless you're a pretty advanced Haskell programmer, understand the implications, and have a good reason it's worth it. But you can import System.IO.Unsafe and then wrap the definition of getBounds with the unsafePerformIO function. What this says is: "Pretend that getBounds is some specific String rather than a procedure. Any time you need to know which String it is, go sneak off behind the scenes and run this procedure to find out."
Note that if you do this, there is NO guarantee about when your program will read the file, how many times your program will read the file, or even whether your program will read the file at all! All of these things can depend on things like strictness of evaluation, which optimization choices like inlining the compiler makes, and so on. If the contents of the file change while your program is running, the result is undefined. This is not a normal way to write Haskell, other Haskell programmers will give you weird looks and assume you're kind of clueless if you do it without a good reason, and you should probably just forget about it until you're more on the advanced side. I only mentioned it because I don't want to give an incomplete response.